├── .github └── workflows │ ├── build.yml │ └── release.yml ├── .gitignore ├── COPYING ├── ChangeLog.md ├── Makefile.am ├── README.md ├── autogen.sh ├── configure.ac ├── include ├── Makefile.am └── linux │ ├── bitfield.h │ ├── devlink.h │ └── mdio-netlink.h ├── kernel ├── Makefile ├── compat.h └── mdio-netlink.c ├── m4 └── .gitignore ├── man ├── Makefile.am ├── mdio-netlink.9 ├── mdio.8 └── mvls.8 └── src ├── Makefile.am ├── mdio ├── .gitignore ├── Makefile.am ├── bus.c ├── cmds.ld ├── main.c ├── mdio.c ├── mdio.h ├── mva.c ├── mvls.c ├── phy.c ├── print_phy.c └── xrs.c └── mvls ├── .gitignore ├── Makefile.am ├── devlink.c ├── devlink.h ├── mvls-json.c ├── mvls-show.c ├── mvls.c ├── mvls.h └── queue.h /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Bob the Builder 2 | 3 | # Run on all branches, including all pull requests, except the 'dev' 4 | # branch since that's where we run Coverity Scan (limited tokens/day) 5 | on: 6 | push: 7 | branches: 8 | - '**' 9 | - '!dev' 10 | pull_request: 11 | branches: 12 | - '**' 13 | 14 | jobs: 15 | build: 16 | # Verify we can build on latest Ubuntu with both gcc and clang 17 | name: ${{ matrix.compiler }} 18 | runs-on: ubuntu-latest 19 | strategy: 20 | matrix: 21 | compiler: [gcc, clang] 22 | fail-fast: false 23 | env: 24 | MAKEFLAGS: -j3 25 | CC: ${{ matrix.compiler }} 26 | steps: 27 | - name: Install dependencies 28 | run: | 29 | sudo apt-get -y update 30 | sudo apt-get -y install pkg-config libmnl-dev tree 31 | - uses: actions/checkout@v2 32 | - name: Configure 33 | # Build in a sub-directory so we can safely set a+w on all 34 | # directories. Needed for `make check` since it runs with 35 | # root dropped and wants to write .trs and .log files. 36 | run: | 37 | ./autogen.sh 38 | ./configure -prefix= 39 | - name: Build 40 | run: | 41 | make 42 | - name: Install 43 | run: | 44 | DESTDIR=~/tmp make install-strip 45 | tree ~/tmp 46 | ldd ~/tmp/sbin/mvls 47 | size ~/tmp/sbin/mvls 48 | ldd ~/tmp/sbin/mdio 49 | size ~/tmp/sbin/mdio 50 | ~/tmp/sbin/mvls -h 51 | ~/tmp/sbin/mdio -h 52 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release General 2 | 3 | on: 4 | push: 5 | tags: 6 | - '[0-9]+.[0-9]+.[0-9]+' 7 | 8 | jobs: 9 | release: 10 | name: Create GitHub release 11 | runs-on: ubuntu-latest 12 | if: startsWith(github.ref, 'refs/tags/') 13 | outputs: 14 | upload_url: ${{ steps.create_release.outputs.upload_url }} 15 | release_id: ${{ steps.create_release.outputs.id }} 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Extract ChangeLog entry ... 19 | # Hack to extract latest entry for body_path below 20 | run: | 21 | awk '/-----*/{if (x == 1) exit; x=1;next}x' ChangeLog.md \ 22 | |head -n -1 > release.md 23 | cat release.md 24 | - name: Create release ... 25 | id: create_release 26 | uses: actions/create-release@v1 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | with: 30 | tag_name: ${{ github.ref }} 31 | release_name: mdio-tools v${{ github.ref }} 32 | body_path: release.md 33 | draft: false 34 | prerelease: false 35 | tarball: 36 | name: Build and upload release tarball 37 | needs: release 38 | if: startsWith(github.ref, 'refs/tags/') 39 | runs-on: ubuntu-latest 40 | steps: 41 | - uses: actions/checkout@v2 42 | - name: Installing dependencies ... 43 | run: | 44 | sudo apt-get -y update 45 | sudo apt-get -y install pkg-config libmnl-dev 46 | - name: Creating Makefiles ... 47 | run: | 48 | ./autogen.sh 49 | ./configure --prefix= 50 | - name: Build release ... 51 | run: | 52 | make release 53 | ls -lF ../ 54 | mkdir -p artifacts/ 55 | mv ../*.tar.* artifacts/ 56 | - name: Upload release artifacts ... 57 | uses: skx/github-action-publish-binaries@release-0.15 58 | env: 59 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 60 | with: 61 | releaseId: ${{ needs.release.outputs.release_id }} 62 | args: artifacts/* 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | aclocal.m4 3 | autom4te.cache/ 4 | aux/ 5 | config.h 6 | config.h.in 7 | config.log 8 | config.status 9 | configure 10 | GPATH 11 | GRTAGS 12 | GTAGS 13 | Makefile 14 | Makefile.in 15 | stamp-h1 16 | tags 17 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /ChangeLog.md: -------------------------------------------------------------------------------- 1 | ChangeLog 2 | ========= 3 | 4 | All notable changes to the project are documented in this file. 5 | 6 | [v1.3.1] - 2023-12-02 7 | --------------------- 8 | 9 | Fixes mvls to work with kernels 6.2 and onwards. 10 | 11 | ### Added 12 | - mdio: Multiple registers can now be dumped at once, via the generic 13 | dump operation. 14 | 15 | ### Fixed 16 | - mvls: Relax the driver matching to accept the strings used in 17 | kernels 6.2 and newer. 18 | 19 | 20 | [v1.3.0] - 2023-07-24 21 | --------------------- 22 | 23 | Primarily widen the gamut of supported kernel versions, now supporting 24 | all kernels from 5.2 and onwards. 25 | 26 | ### Added 27 | - mvls: Support for 88E6320/88E6321 28 | 29 | ### Changed 30 | - mdio-netlink: Adapt to the upstream C22/C45 refactor. 31 | 32 | 33 | [v1.2.0] - 2022-09-15 34 | --------------------- 35 | 36 | ### Added 37 | - mdio: A new addressing mode "mmd-c22": Used to access MMDs attached 38 | to MDIO controllers without Clause 45 support by using registers 13 39 | and 14 in the device's Clause 22 register space 40 | - mdio: Pretty print gigabit link capability information from a PHY's 41 | extended status register 42 | - mdio: Pretty print lots of status information from MMDs (C45 PHYs) 43 | - mvls: Decode priority override information of ATU entries 44 | 45 | ### Changed 46 | - mvls: Table listings now always prints out the device information, 47 | even on single chip systems. 48 | 49 | [v1.1.1] - 2022-05-23 50 | --------------------- 51 | 52 | Tiny bugfix release. 53 | 54 | ### Fixed 55 | - mdio: The bench operation is now much more reliable when stacked on 56 | other devices than regular PHYs (e.g. paged PHYs or Marvell 57 | switches). 58 | - mvls: The STU can now be dumped chips from the Peridot generation. 59 | 60 | [v1.1.0] - 2022-05-04 61 | --------------------- 62 | 63 | A sprawling release, adding various mvls related introspection 64 | features. mvls also gains a JSON output format. 65 | 66 | ### Added 67 | - mvls: The STU can now be dumped (requires Linux 5.17 or later). This 68 | is useful now that mv88e6xxx supports offloading of MST states 69 | - mvls: Output can now be formatted as JSON for easier scripting 70 | - mdio: mvls: A subset of MIB counters can now be dumped. This let's 71 | you get at counters for DSA ports, which are not reachable from 72 | ethtool 73 | - mdio: mvls: The LAG mask and LAG map tables can now be dumped 74 | - mdio: Improve usage message by including the examples from the 75 | manual 76 | 77 | [v1.0.1] - 2021-11-26 78 | --------------------- 79 | 80 | Primarily fixes a few issues in the kernel module that were found 81 | during a quick review from Russell King: 82 | 83 | https://lore.kernel.org/netdev/YYPThd7aX+TBWslz@shell.armlinux.org.uk/ 84 | https://lore.kernel.org/netdev/YYPU1gOvUPa00JWg@shell.armlinux.org.uk/ 85 | 86 | ### Added 87 | - mdio: The mvls subcommand now supports flushing the ATU 88 | 89 | ### Fixed 90 | - mdio-netlink: Plug some glaring holes around integer overflows of 91 | the PC. 92 | - mdio-netlink: Release reference to MDIO bus after a transaction 93 | completes. 94 | 95 | 96 | [v1.0.0] - 2021-09-17 97 | --------------------- 98 | 99 | ### Added 100 | - Basic usage text, `mvls -h` 101 | - Manuals 102 | 103 | ### Changes 104 | - Reworked command syntax to be more ergonomic 105 | - Improved error output 106 | 107 | ### Fixes 108 | - Fix #4: buffer alignment on Arm9 systems 109 | 110 | ### Removed 111 | - References to the dump operation in mdio(8) which is not supported 112 | at the moment 113 | 114 | v1.0.0-beta1 - 2021-05-21 115 | ------------------------- 116 | 117 | Initial public release. 118 | 119 | 120 | [UNRELEASED]: https://github.com/wkz/mdio-tools/compare/1.0.0-beta1...HEAD 121 | [v1.0.0]: https://github.com/wkz/mdio-tools/compare/1.0.0-beta1...1.0.0 122 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | SUBDIRS = man src include 2 | doc_DATA = README.md ChangeLog.md COPYING 3 | EXTRA_DIST = $(doc_DATA) kernel 4 | DISTCLEANFILES = *~ *.d 5 | ACLOCAL_AMFLAGS = -I m4 6 | 7 | TAG = $(top_srcdir)/.git/refs/tags/$(PACKAGE_VERSION) 8 | 9 | $(TAG): 10 | @printf "\e[1m\e[41mPlease create the \"$(PACKAGE_VERSION)\" tag first\e[0m\n" 11 | @exit 1 12 | 13 | release: $(TAG) distcheck 14 | @git status 15 | @for file in $(DIST_ARCHIVES); do \ 16 | md5sum $$file > ../$$file.md5; \ 17 | sha1sum $$file > ../$$file.sha1; \ 18 | sha256sum $$file > ../$$file.sha256; \ 19 | done 20 | @mv $(DIST_ARCHIVES) ../ 21 | @echo 22 | @echo "Resulting release files:" 23 | @echo "=================================================================" 24 | @for file in $(DIST_ARCHIVES); do \ 25 | printf "$$file \tDistribution tarball\n"; \ 26 | printf "$$file.md5\t"; cat ../$$file.md5 | cut -f1 -d' '; \ 27 | printf "$$file.sha1\t"; cat ../$$file.sha1 | cut -f1 -d' '; \ 28 | printf "$$file.sha256\t"; cat ../$$file.sha256 | cut -f1 -d' '; \ 29 | done 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | mdio-tools 2 | ========== 3 | [![License Badge][]][License] [![GitHub Status][]][GitHub] 4 | 5 | The latest release is always available from GitHub at 6 | > https://github.com/wkz/mdio-tools/releases 7 | 8 | 9 | Table of Contents 10 | ----------------- 11 | * [Introduction](#introduction) 12 | * [Usage](#usage) 13 | * [Build](#build) 14 | 15 | 16 | Introduction 17 | ------------ 18 | 19 | `mdio` is a low-level Linux debug tool for communicating with devices 20 | attached an MDIO bus. It improves on existing tools in this space in a 21 | few important ways: 22 | 23 | - MDIO buses are directly addressable. Previous solutions relied on at 24 | least one Ethernet PHY on the bus being attached to a net device, 25 | which is typically not the case when the device is an Ethernet 26 | switch for example. 27 | - Complex operations can be performed atomically. The old API only 28 | supported a single read or write of a single register. `mdio` sends 29 | byte code to the `mdio-netlink` kernel module that can perform 30 | multiple operations, store intermediate values, loop etc. As a 31 | result, things like read/mask/write operations and accesses to paged 32 | PHYs can be performed safely. 33 | 34 | 35 | Usage 36 | ----- 37 | 38 | ``` 39 | mdio -- List available buses 40 | mdio BUS -- Probe BUS for active devices 41 | mdio BUS OBJ -- Show status of OBJ 42 | mdio BUS OBJ OP -- Perform OP on OBJ 43 | 44 | Options: 45 | -h This help text 46 | -v Show verision and contact information 47 | 48 | Bus names may be abbreviated using glob(3) syntax, i.e. "fixed*" 49 | would typically match against "fixed-0". 50 | 51 | Objects: 52 | phy PHYAD 53 | Clause 22 (MDIO) PHY using address PHYAD. 54 | 55 | REG: u5 56 | 57 | mmd PRTAD[:DEVAD] 58 | Clause 45 (XMDIO) PHY using address PRTAD:DEVAD. 59 | 60 | REG: u16 61 | 62 | mva PHYAD 63 | Operate on Marvell Alaska (mv88e8xxx) PHY using address PHYAD. 64 | Register 22 is assumed to be the page register. 65 | 66 | REG: u8|"copper"|"fiber":u5 67 | 68 | mvls ID 69 | Operate on Marvell LinkStreet (mv88e6xxx) device attached to BUS 70 | using address ID. If ID is 0, single-chip addressing is used; all 71 | other IDs use multi-chip addressing. 72 | 73 | REG: u5|"global1"|"global2" u5 74 | 75 | xrs PHYAD 76 | Operate of Arrow/Flexibilis XRS700x device using address PHYAD. 77 | 78 | REG: u32 (Stride of 2, only even registers are valid) 79 | 80 | Operations: 81 | raw REG [DATA[/MASK]] 82 | Raw register access. Without DATA, REG is read. An unmasked DATA will 83 | do a single write to REG. A masked DATA will perform a read/mask/write 84 | sequence. 85 | 86 | DATA: u16 87 | MASK: u16 88 | ``` 89 | 90 | Build 91 | ----- 92 | 93 | At the moment, the kernel module (which requires at least kernel version 5.2) 94 | has to be built separately. Set `KDIR` if building against a kernel in a 95 | non-standard location. 96 | 97 | cd kernel/ 98 | make all && sudo make install 99 | 100 | When building from GIT, the `configure` script first needs to be generated, this 101 | requires `autoconf` and `automake` to be installed. A helper script to generate 102 | configure is available: 103 | 104 | ./autogen.sh 105 | 106 | Standard autotools incantation is then used, requires `pkg-config` to locate the 107 | `libmnl` development files. 108 | 109 | ./configure --prefix=/usr && make all && sudo make install 110 | 111 | [License]: https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html 112 | [License Badge]: https://img.shields.io/badge/License-GPL%20v2-blue.svg 113 | [GitHub]: https://github.com/wkz/mdio-tools/actions/workflows/build.yml/ 114 | [GitHub Status]: https://github.com/wkz/mdio-tools/actions/workflows/build.yml/badge.svg 115 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | autoreconf -W portability -vifm 4 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | AC_PREREQ(2.61) 2 | AC_INIT(mdio-tools, m4_esyscmd_s(git describe --always --dirty --tags), 3 | https://github.com/wkz/mdio-tools/issues) 4 | AC_CONFIG_AUX_DIR(aux) 5 | AM_INIT_AUTOMAKE(1.11 foreign subdir-objects) 6 | AM_SILENT_RULES(yes) 7 | 8 | AC_CONFIG_HEADERS([config.h]) 9 | AC_CONFIG_FILES([ 10 | Makefile 11 | include/Makefile 12 | man/Makefile 13 | src/Makefile 14 | src/mdio/Makefile 15 | src/mvls/Makefile 16 | ]) 17 | AC_CONFIG_MACRO_DIRS(m4) 18 | 19 | AC_PROG_CC 20 | AC_PROG_INSTALL 21 | 22 | PKG_PROG_PKG_CONFIG 23 | PKG_CHECK_MODULES([mnl], [libmnl >= 0.2.0]) 24 | 25 | AC_OUTPUT 26 | -------------------------------------------------------------------------------- /include/Makefile.am: -------------------------------------------------------------------------------- 1 | EXTRA_DIST = linux/bitfield.h linux/devlink.h linux/mdio-netlink.h 2 | -------------------------------------------------------------------------------- /include/linux/bitfield.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | /* 3 | * Copyright (C) 2014 Felix Fietkau 4 | * Copyright (C) 2004 - 2009 Ivo van Doorn 5 | */ 6 | 7 | #ifndef _LINUX_BITFIELD_H 8 | #define _LINUX_BITFIELD_H 9 | 10 | #include 11 | 12 | /* 13 | * Bitfield access macros 14 | * 15 | * FIELD_{GET,PREP} macros take as first parameter shifted mask 16 | * from which they extract the base mask and shift amount. 17 | * Mask must be a compilation time constant. 18 | * 19 | * Example: 20 | * 21 | * #include 22 | * #include 23 | * 24 | * #define REG_FIELD_A GENMASK(6, 0) 25 | * #define REG_FIELD_B BIT(7) 26 | * #define REG_FIELD_C GENMASK(15, 8) 27 | * #define REG_FIELD_D GENMASK(31, 16) 28 | * 29 | * Get: 30 | * a = FIELD_GET(REG_FIELD_A, reg); 31 | * b = FIELD_GET(REG_FIELD_B, reg); 32 | * 33 | * Set: 34 | * reg = FIELD_PREP(REG_FIELD_A, 1) | 35 | * FIELD_PREP(REG_FIELD_B, 0) | 36 | * FIELD_PREP(REG_FIELD_C, c) | 37 | * FIELD_PREP(REG_FIELD_D, 0x40); 38 | * 39 | * Modify: 40 | * reg &= ~REG_FIELD_C; 41 | * reg |= FIELD_PREP(REG_FIELD_C, c); 42 | */ 43 | 44 | #define __bf_shf(x) (__builtin_ffsll(x) - 1) 45 | 46 | #define __BF_FIELD_CHECK(_mask, _reg, _val, _pfx) 47 | 48 | /** 49 | * FIELD_MAX() - produce the maximum value representable by a field 50 | * @_mask: shifted mask defining the field's length and position 51 | * 52 | * FIELD_MAX() returns the maximum value that can be held in the field 53 | * specified by @_mask. 54 | */ 55 | #define FIELD_MAX(_mask) \ 56 | ({ \ 57 | __BF_FIELD_CHECK(_mask, 0ULL, 0ULL, "FIELD_MAX: "); \ 58 | (typeof(_mask))((_mask) >> __bf_shf(_mask)); \ 59 | }) 60 | 61 | /** 62 | * FIELD_FIT() - check if value fits in the field 63 | * @_mask: shifted mask defining the field's length and position 64 | * @_val: value to test against the field 65 | * 66 | * Return: true if @_val can fit inside @_mask, false if @_val is too big. 67 | */ 68 | #define FIELD_FIT(_mask, _val) \ 69 | ({ \ 70 | __BF_FIELD_CHECK(_mask, 0ULL, 0ULL, "FIELD_FIT: "); \ 71 | !((((typeof(_mask))_val) << __bf_shf(_mask)) & ~(_mask)); \ 72 | }) 73 | 74 | /** 75 | * FIELD_PREP() - prepare a bitfield element 76 | * @_mask: shifted mask defining the field's length and position 77 | * @_val: value to put in the field 78 | * 79 | * FIELD_PREP() masks and shifts up the value. The result should 80 | * be combined with other fields of the bitfield using logical OR. 81 | */ 82 | #define FIELD_PREP(_mask, _val) \ 83 | ({ \ 84 | __BF_FIELD_CHECK(_mask, 0ULL, _val, "FIELD_PREP: "); \ 85 | ((typeof(_mask))(_val) << __bf_shf(_mask)) & (_mask); \ 86 | }) 87 | 88 | /** 89 | * FIELD_GET() - extract a bitfield element 90 | * @_mask: shifted mask defining the field's length and position 91 | * @_reg: value of entire bitfield 92 | * 93 | * FIELD_GET() extracts the field specified by @_mask from the 94 | * bitfield passed in as @_reg by masking and shifting it down. 95 | */ 96 | #define FIELD_GET(_mask, _reg) \ 97 | ({ \ 98 | __BF_FIELD_CHECK(_mask, _reg, 0U, "FIELD_GET: "); \ 99 | (typeof(_mask))(((_reg) & (_mask)) >> __bf_shf(_mask)); \ 100 | }) 101 | 102 | extern void __field_overflow(void); 103 | extern void __bad_mask(void); 104 | static __always_inline uint64_t field_multiplier(uint64_t field) 105 | { 106 | if ((field | (field - 1)) & ((field | (field - 1)) + 1)) 107 | __bad_mask(); 108 | return field & -field; 109 | } 110 | static __always_inline uint64_t field_mask(uint64_t field) 111 | { 112 | return field / field_multiplier(field); 113 | } 114 | #define field_max(field) ((typeof(field))field_mask(field)) 115 | #define ____MAKE_OP(type,base,to,from) \ 116 | static __always_inline type type##_encode_bits(base v, base field) \ 117 | { \ 118 | if (__builtin_constant_p(v) && (v & ~field_mask(field))) \ 119 | __field_overflow(); \ 120 | return to((v & field_mask(field)) * field_multiplier(field)); \ 121 | } \ 122 | static __always_inline type type##_replace_bits(type old, \ 123 | base val, base field) \ 124 | { \ 125 | return (old & ~to(field)) | type##_encode_bits(val, field); \ 126 | } \ 127 | static __always_inline void type##p_replace_bits(type *p, \ 128 | base val, base field) \ 129 | { \ 130 | *p = (*p & ~to(field)) | type##_encode_bits(val, field); \ 131 | } \ 132 | static __always_inline base type##_get_bits(type v, base field) \ 133 | { \ 134 | return (from(v) & field)/field_multiplier(field); \ 135 | } 136 | #define __MAKE_OP(size) \ 137 | ____MAKE_OP(uint##size##_t,uint##size##_t,,) 138 | __MAKE_OP(8) 139 | __MAKE_OP(16) 140 | __MAKE_OP(32) 141 | __MAKE_OP(64) 142 | #undef __MAKE_OP 143 | #undef ____MAKE_OP 144 | 145 | #endif 146 | -------------------------------------------------------------------------------- /include/linux/mdio-netlink.h: -------------------------------------------------------------------------------- 1 | #ifndef __MDIO_NETLINK_H__ 2 | #define __MDIO_NETLINK_H__ 3 | 4 | #include 5 | 6 | enum { 7 | MDIO_GENL_UNSPEC, 8 | MDIO_GENL_XFER, 9 | 10 | __MDIO_GENL_MAX, 11 | MDIO_GENL_MAX = __MDIO_GENL_MAX - 1 12 | }; 13 | 14 | enum { 15 | MDIO_NLA_UNSPEC, 16 | MDIO_NLA_BUS_ID, /* string */ 17 | MDIO_NLA_TIMEOUT, /* u32 */ 18 | MDIO_NLA_PROG, /* struct mdio_nl_insn[] */ 19 | MDIO_NLA_DATA, /* nest */ 20 | MDIO_NLA_ERROR, /* s32 */ 21 | 22 | __MDIO_NLA_MAX, 23 | MDIO_NLA_MAX = __MDIO_NLA_MAX - 1 24 | }; 25 | 26 | enum mdio_nl_op { 27 | MDIO_NL_OP_UNSPEC, 28 | MDIO_NL_OP_READ, /* read dev(RI), port(RI), dst(R) */ 29 | MDIO_NL_OP_WRITE, /* write dev(RI), port(RI), src(RI) */ 30 | MDIO_NL_OP_AND, /* and a(RI), b(RI), dst(R) */ 31 | MDIO_NL_OP_OR, /* or a(RI), b(RI), dst(R) */ 32 | MDIO_NL_OP_ADD, /* add a(RI), b(RI), dst(R) */ 33 | MDIO_NL_OP_JEQ, /* jeq a(RI), b(RI), jmp(I) */ 34 | MDIO_NL_OP_JNE, /* jeq a(RI), b(RI), jmp(I) */ 35 | MDIO_NL_OP_EMIT, /* emit src(RI) */ 36 | 37 | __MDIO_NL_OP_MAX, 38 | MDIO_NL_OP_MAX = __MDIO_NL_OP_MAX - 1 39 | }; 40 | 41 | enum mdio_nl_argmode { 42 | MDIO_NL_ARG_NONE, 43 | MDIO_NL_ARG_REG, 44 | MDIO_NL_ARG_IMM, 45 | MDIO_NL_ARG_RESERVED 46 | }; 47 | 48 | struct mdio_nl_insn { 49 | __u64 op:8; 50 | __u64 reserved:2; 51 | __u64 arg0:18; 52 | __u64 arg1:18; 53 | __u64 arg2:18; 54 | }; 55 | 56 | #endif /* __MDIO_NETLINK_H__ */ 57 | -------------------------------------------------------------------------------- /kernel/Makefile: -------------------------------------------------------------------------------- 1 | obj-m := mdio-netlink.o 2 | ccflags-y := -I$(src)/../include 3 | 4 | KDIR ?= /lib/modules/$(shell uname -r)/build 5 | 6 | all: 7 | $(MAKE) -C $(KDIR) M=$(CURDIR) modules 8 | 9 | install: all 10 | $(MAKE) -C $(KDIR) M=$(CURDIR) modules_install 11 | 12 | clean: 13 | $(MAKE) -C $(KDIR) M=$(CURDIR) clean 14 | -------------------------------------------------------------------------------- /kernel/compat.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #ifndef _COMPAT_H_ 4 | #define _COMPAT_H_ 5 | 6 | #include 7 | 8 | #if LINUX_VERSION_CODE < KERNEL_VERSION(5,8,0) 9 | #include 10 | #include 11 | 12 | static inline u32 mdiobus_c45_addr(int devad, u16 regnum) 13 | { 14 | return MII_ADDR_C45 | devad << MII_DEVADDR_C45_SHIFT | regnum; 15 | } 16 | 17 | static inline int __mdiobus_c45_read(struct mii_bus *bus, int prtad, int devad, 18 | u16 regnum) 19 | { 20 | return __mdiobus_read(bus, prtad, mdiobus_c45_addr(devad, regnum)); 21 | } 22 | 23 | static inline int __mdiobus_c45_write(struct mii_bus *bus, int prtad, int devad, 24 | u16 regnum, u16 val) 25 | { 26 | return __mdiobus_write(bus, prtad, mdiobus_c45_addr(devad, regnum), 27 | val); 28 | } 29 | 30 | #endif /* < 5.8.0 */ 31 | 32 | #if LINUX_VERSION_CODE < KERNEL_VERSION(5,7,0) 33 | #include 34 | #include 35 | 36 | #if LINUX_VERSION_CODE < KERNEL_VERSION(5,4,0) 37 | #include 38 | 39 | static inline int _mdio_find_bus_match(struct device *dev, void *data) 40 | { 41 | return dev->parent && sysfs_streq(dev_name(dev->parent), data); 42 | } 43 | #else 44 | static inline int _mdio_find_bus_match(struct device *dev, const void *data) 45 | { 46 | return dev->parent && device_match_name(dev->parent, data); 47 | } 48 | #endif /* < 5.4.0 */ 49 | 50 | /** 51 | * mdio_find_bus - Given the name of a mdiobus, find the mii_bus. 52 | * @mdio_bus_np: Pointer to the mii_bus. 53 | * 54 | * Returns a reference to the mii_bus, or NULL if none found. The 55 | * embedded struct device will have its reference count incremented, 56 | * and this must be put_deviced'ed once the bus is finished with. 57 | * 58 | * Abuse bus_find_device() to * reimplement the mdio_find_bus() 59 | * function introduced in v5.7. 60 | */ 61 | static inline struct mii_bus *mdio_find_bus(const char *mdio_name) 62 | { 63 | struct device *d, *p = NULL; 64 | 65 | d = bus_find_device(&mdio_bus_type, NULL, (char *)mdio_name, _mdio_find_bus_match); 66 | if (!d) 67 | return NULL; 68 | if (d->parent) 69 | p = get_device(d->parent); 70 | put_device(d); 71 | return p ? to_mii_bus(p) : NULL; 72 | } 73 | #endif /* < 5.7.0 */ 74 | 75 | #endif /* _COMPAT_H_ */ 76 | -------------------------------------------------------------------------------- /kernel/mdio-netlink.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "compat.h" 12 | 13 | struct mdio_nl_xfer { 14 | struct genl_info *info; 15 | struct sk_buff *msg; 16 | void *hdr; 17 | struct nlattr *data; 18 | 19 | struct mii_bus *mdio; 20 | int timeout_ms; 21 | 22 | int prog_len; 23 | struct mdio_nl_insn *prog; 24 | }; 25 | 26 | static int mdio_nl_open(struct mdio_nl_xfer *xfer); 27 | static int mdio_nl_close(struct mdio_nl_xfer *xfer, bool last, int xerr); 28 | 29 | static int mdio_nl_flush(struct mdio_nl_xfer *xfer) 30 | { 31 | int err; 32 | 33 | err = mdio_nl_close(xfer, false, 0); 34 | if (err) 35 | return err; 36 | 37 | return mdio_nl_open(xfer); 38 | } 39 | 40 | static int mdio_nl_emit(struct mdio_nl_xfer *xfer, u32 datum) 41 | { 42 | int err = 0; 43 | 44 | if (!nla_put_nohdr(xfer->msg, sizeof(datum), &datum)) 45 | return 0; 46 | 47 | err = mdio_nl_flush(xfer); 48 | if (err) 49 | return err; 50 | 51 | return nla_put_nohdr(xfer->msg, sizeof(datum), &datum); 52 | } 53 | 54 | static inline u16 *__arg_r(u32 arg, u16 *regs) 55 | { 56 | BUG_ON(arg >> 16 != MDIO_NL_ARG_REG); 57 | 58 | return ®s[arg & 0x7]; 59 | } 60 | 61 | static inline u16 __arg_i(u32 arg) 62 | { 63 | BUG_ON(arg >> 16 != MDIO_NL_ARG_IMM); 64 | 65 | return arg & 0xffff; 66 | } 67 | 68 | static inline u16 __arg_ri(u32 arg, u16 *regs) 69 | { 70 | switch ((enum mdio_nl_argmode)(arg >> 16)) { 71 | case MDIO_NL_ARG_IMM: 72 | return arg & 0xffff; 73 | case MDIO_NL_ARG_REG: 74 | return regs[arg & 7]; 75 | default: 76 | BUG(); 77 | } 78 | } 79 | 80 | static int mdio_nl_eval(struct mdio_nl_xfer *xfer) 81 | { 82 | struct mdio_nl_insn *insn; 83 | unsigned long timeout; 84 | u16 regs[8] = { 0 }; 85 | unsigned int pc; 86 | int ret = 0; 87 | 88 | timeout = jiffies + msecs_to_jiffies(xfer->timeout_ms); 89 | 90 | mutex_lock(&xfer->mdio->mdio_lock); 91 | 92 | for (insn = xfer->prog, pc = 0; 93 | pc < xfer->prog_len; 94 | insn = &xfer->prog[++pc]) { 95 | if (time_after(jiffies, timeout)) { 96 | ret = -ETIMEDOUT; 97 | break; 98 | } 99 | 100 | switch ((enum mdio_nl_op)insn->op) { 101 | case MDIO_NL_OP_READ: 102 | if (mdio_phy_id_is_c45(__arg_ri(insn->arg0, regs))) 103 | ret = __mdiobus_c45_read(xfer->mdio, 104 | mdio_phy_id_prtad(__arg_ri(insn->arg0, regs)), 105 | mdio_phy_id_devad(__arg_ri(insn->arg0, regs)), 106 | __arg_ri(insn->arg1, regs)); 107 | else 108 | ret = __mdiobus_read(xfer->mdio, 109 | __arg_ri(insn->arg0, regs), 110 | __arg_ri(insn->arg1, regs)); 111 | if (ret < 0) 112 | goto exit; 113 | *__arg_r(insn->arg2, regs) = ret; 114 | ret = 0; 115 | break; 116 | 117 | case MDIO_NL_OP_WRITE: 118 | if (mdio_phy_id_is_c45(__arg_ri(insn->arg0, regs))) 119 | ret = __mdiobus_c45_write(xfer->mdio, 120 | mdio_phy_id_prtad(__arg_ri(insn->arg0, regs)), 121 | mdio_phy_id_devad(__arg_ri(insn->arg0, regs)), 122 | __arg_ri(insn->arg1, regs), 123 | __arg_ri(insn->arg2, regs)); 124 | else 125 | ret = __mdiobus_write(xfer->mdio, 126 | __arg_ri(insn->arg0, regs), 127 | __arg_ri(insn->arg1, regs), 128 | __arg_ri(insn->arg2, regs)); 129 | if (ret < 0) 130 | goto exit; 131 | ret = 0; 132 | break; 133 | 134 | case MDIO_NL_OP_AND: 135 | *__arg_r(insn->arg2, regs) = 136 | __arg_ri(insn->arg0, regs) & 137 | __arg_ri(insn->arg1, regs); 138 | break; 139 | 140 | case MDIO_NL_OP_OR: 141 | *__arg_r(insn->arg2, regs) = 142 | __arg_ri(insn->arg0, regs) | 143 | __arg_ri(insn->arg1, regs); 144 | break; 145 | 146 | case MDIO_NL_OP_ADD: 147 | *__arg_r(insn->arg2, regs) = 148 | __arg_ri(insn->arg0, regs) + 149 | __arg_ri(insn->arg1, regs); 150 | break; 151 | 152 | case MDIO_NL_OP_JEQ: 153 | if (__arg_ri(insn->arg0, regs) == 154 | __arg_ri(insn->arg1, regs)) 155 | pc += (s16)__arg_i(insn->arg2); 156 | break; 157 | 158 | case MDIO_NL_OP_JNE: 159 | if (__arg_ri(insn->arg0, regs) != 160 | __arg_ri(insn->arg1, regs)) 161 | pc += (s16)__arg_i(insn->arg2); 162 | break; 163 | 164 | case MDIO_NL_OP_EMIT: 165 | ret = mdio_nl_emit(xfer, __arg_ri(insn->arg0, regs)); 166 | if (ret < 0) 167 | goto exit; 168 | ret = 0; 169 | break; 170 | 171 | case MDIO_NL_OP_UNSPEC: 172 | default: 173 | ret = -EINVAL; 174 | goto exit; 175 | } 176 | } 177 | exit: 178 | mutex_unlock(&xfer->mdio->mdio_lock); 179 | return ret; 180 | } 181 | 182 | struct mdio_nl_op_proto { 183 | u8 arg0; 184 | u8 arg1; 185 | u8 arg2; 186 | }; 187 | 188 | static const struct mdio_nl_op_proto mdio_nl_op_protos[MDIO_NL_OP_MAX + 1] = { 189 | [MDIO_NL_OP_READ] = { 190 | .arg0 = BIT(MDIO_NL_ARG_REG) | BIT(MDIO_NL_ARG_IMM), 191 | .arg1 = BIT(MDIO_NL_ARG_REG) | BIT(MDIO_NL_ARG_IMM), 192 | .arg2 = BIT(MDIO_NL_ARG_REG), 193 | }, 194 | [MDIO_NL_OP_WRITE] = { 195 | .arg0 = BIT(MDIO_NL_ARG_REG) | BIT(MDIO_NL_ARG_IMM), 196 | .arg1 = BIT(MDIO_NL_ARG_REG) | BIT(MDIO_NL_ARG_IMM), 197 | .arg2 = BIT(MDIO_NL_ARG_REG) | BIT(MDIO_NL_ARG_IMM), 198 | }, 199 | [MDIO_NL_OP_AND] = { 200 | .arg0 = BIT(MDIO_NL_ARG_REG) | BIT(MDIO_NL_ARG_IMM), 201 | .arg1 = BIT(MDIO_NL_ARG_REG) | BIT(MDIO_NL_ARG_IMM), 202 | .arg2 = BIT(MDIO_NL_ARG_REG), 203 | }, 204 | [MDIO_NL_OP_OR] = { 205 | .arg0 = BIT(MDIO_NL_ARG_REG) | BIT(MDIO_NL_ARG_IMM), 206 | .arg1 = BIT(MDIO_NL_ARG_REG) | BIT(MDIO_NL_ARG_IMM), 207 | .arg2 = BIT(MDIO_NL_ARG_REG), 208 | }, 209 | [MDIO_NL_OP_ADD] = { 210 | .arg0 = BIT(MDIO_NL_ARG_REG) | BIT(MDIO_NL_ARG_IMM), 211 | .arg1 = BIT(MDIO_NL_ARG_REG) | BIT(MDIO_NL_ARG_IMM), 212 | .arg2 = BIT(MDIO_NL_ARG_REG), 213 | }, 214 | [MDIO_NL_OP_JEQ] = { 215 | .arg0 = BIT(MDIO_NL_ARG_REG) | BIT(MDIO_NL_ARG_IMM), 216 | .arg1 = BIT(MDIO_NL_ARG_REG) | BIT(MDIO_NL_ARG_IMM), 217 | .arg2 = BIT(MDIO_NL_ARG_IMM), 218 | }, 219 | [MDIO_NL_OP_JNE] = { 220 | .arg0 = BIT(MDIO_NL_ARG_REG) | BIT(MDIO_NL_ARG_IMM), 221 | .arg1 = BIT(MDIO_NL_ARG_REG) | BIT(MDIO_NL_ARG_IMM), 222 | .arg2 = BIT(MDIO_NL_ARG_IMM), 223 | }, 224 | [MDIO_NL_OP_EMIT] = { 225 | .arg0 = BIT(MDIO_NL_ARG_REG) | BIT(MDIO_NL_ARG_IMM), 226 | .arg1 = BIT(MDIO_NL_ARG_NONE), 227 | .arg2 = BIT(MDIO_NL_ARG_NONE), 228 | }, 229 | }; 230 | 231 | static int mdio_nl_validate_insn(const struct nlattr *attr, 232 | struct netlink_ext_ack *extack, 233 | const struct mdio_nl_insn *insn) 234 | { 235 | const struct mdio_nl_op_proto *proto; 236 | 237 | if (insn->op > MDIO_NL_OP_MAX) { 238 | NL_SET_ERR_MSG_ATTR(extack, attr, "Illegal instruction"); 239 | return -EINVAL; 240 | } 241 | 242 | proto = &mdio_nl_op_protos[insn->op]; 243 | 244 | if (!(BIT(insn->arg0 >> 16) & proto->arg0)) { 245 | NL_SET_ERR_MSG_ATTR(extack, attr, "Argument 0 invalid"); 246 | return -EINVAL; 247 | } 248 | 249 | if (!(BIT(insn->arg1 >> 16) & proto->arg1)) { 250 | NL_SET_ERR_MSG_ATTR(extack, attr, "Argument 1 invalid"); 251 | return -EINVAL; 252 | } 253 | 254 | if (!(BIT(insn->arg2 >> 16) & proto->arg2)) { 255 | NL_SET_ERR_MSG_ATTR(extack, attr, "Argument 2 invalid"); 256 | return -EINVAL; 257 | } 258 | 259 | return 0; 260 | } 261 | 262 | static int mdio_nl_validate_prog(const struct nlattr *attr, 263 | struct netlink_ext_ack *extack) 264 | { 265 | const struct mdio_nl_insn *prog = nla_data(attr); 266 | int len = nla_len(attr); 267 | int i, err = 0; 268 | 269 | if (len % sizeof(*prog)) { 270 | NL_SET_ERR_MSG_ATTR(extack, attr, "Unaligned instruction"); 271 | return -EINVAL; 272 | } 273 | 274 | len /= sizeof(*prog); 275 | for (i = 0; i < len; i++) { 276 | err = mdio_nl_validate_insn(attr, extack, &prog[i]); 277 | if (err) { 278 | break; 279 | } 280 | } 281 | 282 | return err; 283 | } 284 | 285 | static const struct nla_policy mdio_nl_policy[MDIO_NLA_MAX + 1] = { 286 | [MDIO_NLA_UNSPEC] = { .type = NLA_UNSPEC, }, 287 | [MDIO_NLA_BUS_ID] = { .type = NLA_STRING, .len = MII_BUS_ID_SIZE }, 288 | [MDIO_NLA_TIMEOUT] = NLA_POLICY_MAX(NLA_U16, 10 * MSEC_PER_SEC), 289 | [MDIO_NLA_PROG] = NLA_POLICY_VALIDATE_FN(NLA_BINARY, 290 | mdio_nl_validate_prog, 291 | 0x1000), 292 | [MDIO_NLA_DATA] = { .type = NLA_NESTED }, 293 | [MDIO_NLA_ERROR] = { .type = NLA_S32, }, 294 | }; 295 | 296 | static struct genl_family mdio_nl_family; 297 | 298 | static int mdio_nl_open(struct mdio_nl_xfer *xfer) 299 | { 300 | int err; 301 | 302 | xfer->msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); 303 | if (!xfer->msg) { 304 | err = -ENOMEM; 305 | goto err; 306 | } 307 | 308 | xfer->hdr = genlmsg_put(xfer->msg, xfer->info->snd_portid, 309 | xfer->info->snd_seq, &mdio_nl_family, 310 | NLM_F_ACK | NLM_F_MULTI, MDIO_GENL_XFER); 311 | if (!xfer->hdr) { 312 | err = -EMSGSIZE; 313 | goto err_free; 314 | } 315 | 316 | xfer->data = nla_nest_start(xfer->msg, MDIO_NLA_DATA); 317 | if (!xfer->data) { 318 | err = -EMSGSIZE; 319 | goto err_free; 320 | } 321 | 322 | return 0; 323 | 324 | err_free: 325 | nlmsg_free(xfer->msg); 326 | err: 327 | return err; 328 | } 329 | 330 | static int mdio_nl_close(struct mdio_nl_xfer *xfer, bool last, int xerr) 331 | { 332 | struct nlmsghdr *end; 333 | int err; 334 | 335 | nla_nest_end(xfer->msg, xfer->data); 336 | 337 | if (xerr && nla_put_s32(xfer->msg, MDIO_NLA_ERROR, xerr)) { 338 | err = mdio_nl_flush(xfer); 339 | if (err) 340 | goto err_free; 341 | 342 | if (nla_put_s32(xfer->msg, MDIO_NLA_ERROR, xerr)) { 343 | err = -EMSGSIZE; 344 | goto err_free; 345 | } 346 | } 347 | 348 | genlmsg_end(xfer->msg, xfer->hdr); 349 | 350 | if (last) { 351 | end = nlmsg_put(xfer->msg, xfer->info->snd_portid, 352 | xfer->info->snd_seq, NLMSG_DONE, 0, 353 | NLM_F_ACK | NLM_F_MULTI); 354 | if (!end) { 355 | err = mdio_nl_flush(xfer); 356 | if (err) 357 | goto err_free; 358 | 359 | end = nlmsg_put(xfer->msg, xfer->info->snd_portid, 360 | xfer->info->snd_seq, NLMSG_DONE, 0, 361 | NLM_F_ACK | NLM_F_MULTI); 362 | if (!end) { 363 | err = -EMSGSIZE; 364 | goto err_free; 365 | } 366 | } 367 | } 368 | 369 | return genlmsg_unicast(genl_info_net(xfer->info), xfer->msg, 370 | xfer->info->snd_portid); 371 | 372 | err_free: 373 | nlmsg_free(xfer->msg); 374 | return err; 375 | } 376 | 377 | static int mdio_nl_cmd_xfer(struct sk_buff *skb, struct genl_info *info) 378 | { 379 | struct mdio_nl_xfer xfer; 380 | int err; 381 | 382 | if (!info->attrs[MDIO_NLA_BUS_ID] || 383 | !info->attrs[MDIO_NLA_PROG] || 384 | info->attrs[MDIO_NLA_DATA] || 385 | info->attrs[MDIO_NLA_ERROR]) 386 | return -EINVAL; 387 | 388 | xfer.mdio = mdio_find_bus(nla_data(info->attrs[MDIO_NLA_BUS_ID])); 389 | if (!xfer.mdio) 390 | return -ENODEV; 391 | 392 | if (info->attrs[MDIO_NLA_TIMEOUT]) 393 | xfer.timeout_ms = nla_get_u32(info->attrs[MDIO_NLA_TIMEOUT]); 394 | else 395 | xfer.timeout_ms = 100; 396 | 397 | xfer.info = info; 398 | xfer.prog_len = nla_len(info->attrs[MDIO_NLA_PROG]) / sizeof(*xfer.prog); 399 | xfer.prog = nla_data(info->attrs[MDIO_NLA_PROG]); 400 | 401 | err = mdio_nl_open(&xfer); 402 | if (err) 403 | goto out_put; 404 | 405 | err = mdio_nl_eval(&xfer); 406 | 407 | err = mdio_nl_close(&xfer, true, err); 408 | 409 | out_put: 410 | put_device(&xfer.mdio->dev); 411 | return err; 412 | } 413 | 414 | static const struct genl_ops mdio_nl_ops[] = { 415 | { 416 | .cmd = MDIO_GENL_XFER, 417 | .doit = mdio_nl_cmd_xfer, 418 | .flags = GENL_ADMIN_PERM, 419 | }, 420 | }; 421 | 422 | static struct genl_family mdio_nl_family = { 423 | .name = "mdio", 424 | .version = 1, 425 | .maxattr = MDIO_NLA_MAX, 426 | .netnsok = false, 427 | .module = THIS_MODULE, 428 | .ops = mdio_nl_ops, 429 | .n_ops = ARRAY_SIZE(mdio_nl_ops), 430 | .policy = mdio_nl_policy, 431 | }; 432 | 433 | static int __init mdio_nl_init(void) 434 | { 435 | return genl_register_family(&mdio_nl_family); 436 | } 437 | 438 | static void __exit mdio_nl_exit(void) 439 | { 440 | genl_unregister_family(&mdio_nl_family); 441 | } 442 | 443 | MODULE_AUTHOR("Tobias Waldekranz "); 444 | MODULE_DESCRIPTION("MDIO Netlink Interface"); 445 | MODULE_LICENSE("GPL"); 446 | 447 | module_init(mdio_nl_init); 448 | module_exit(mdio_nl_exit); 449 | -------------------------------------------------------------------------------- /m4/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkz/mdio-tools/cd8a90801974afc64eabea664f15095b87dc289c/m4/.gitignore -------------------------------------------------------------------------------- /man/Makefile.am: -------------------------------------------------------------------------------- 1 | dist_man8_MANS = mdio.8 mvls.8 2 | dist_man9_MANS = mdio-netlink.9 3 | -------------------------------------------------------------------------------- /man/mdio-netlink.9: -------------------------------------------------------------------------------- 1 | .Dd September 17, 2021 2 | .Dt MDIO-NETLINK 9 KM 3 | .Os Linux 4 | .Sh NAME 5 | .Nm mdio-netlink 6 | .Nd Netlink interface to MDIO buses 7 | .Sh SYNOPSIS 8 | .Bd -literal 9 | #include 10 | .Ed 11 | .Sh DESCRIPTION 12 | .Nm 13 | provides a direct interface to communicate with devices on MDIO 14 | buses. In order to provide a race-free way to execute multiple 15 | reads/writes as an atomic transaction, the input is a program in byte 16 | code form that is executed within a virtual machine inside 17 | .Nm . 18 | This is similar in spirit to how 19 | .Dq channel programs 20 | work on some mainframes. The VM has no stack or heap memory, only 8 21 | 16-bit general purpose registers and an implicit program counter. It 22 | provides a small set of instructions: 23 | .Bl -tag -offset 2n 24 | .It Cm READ 25 | Read from MDIO/XMDIO device to register. 26 | .It Cm WRITE 27 | Write register or immediate value to MDIO/XMDIO device. 28 | .It Cm EMIT 29 | Emit the contents of a register or immediate value as an output of the program. 30 | .It Cm AND 31 | Store the bitwise AND of two operands to a register. 32 | .It Cm OR 33 | Store the bitwise OR of two operands to a register. 34 | .It Cm ADD 35 | Store the sum of two operands to a register. 36 | .It Cm JEQ 37 | Add an immediate value to the program counter if two operands are equal. 38 | .It Cm JNE 39 | Add an immediate value to the program counter if two operands are not equal. 40 | .El 41 | .Sh HISTORY 42 | This improves on the traditional MDIO interface available to userspace 43 | programs in Linux in a few important ways: 44 | .Bl -bullet -offset 2n 45 | .It 46 | MDIO buses are directly addressable. Previous solutions relied on at 47 | least one Ethernet PHY on the bus being attached to a net device, 48 | which is typically not the case when the device is an Ethernet switch 49 | for example. 50 | .It 51 | Complex operations can be performed atomically. The old 52 | .Xr ioctl 2 53 | interface only supported a single read or write of a single 54 | register. As a result, things like read/mask/write operations and 55 | accesses to paged PHYs could not be performed safely. 56 | .El 57 | .Sh SEE ALSO 58 | .Xr mdio 8 59 | .Sh AUTHORS 60 | .An Tobias Waldekranz Aq Mt tobias@waldekranz.com 61 | -------------------------------------------------------------------------------- /man/mdio.8: -------------------------------------------------------------------------------- 1 | .Dd September 17, 2021 2 | .Dt MDIO 8 SMM 3 | .Os Linux 4 | .Sh NAME 5 | .Nm mdio 6 | .Nd Direct MDIO bus access 7 | .Sh SYNOPSIS 8 | .Nm mdio 9 | .Op Ar bus Op Ar device Op Ar operation 10 | .Nm mdio 11 | .Op Fl h | Fl v 12 | .Sh DESCRIPTION 13 | Without any arguments, all available MDIO buses are listed. Supplying only 14 | .Ar bus 15 | (see 16 | .Sx Buses ) 17 | will list any Clause 22 addressable devices on it. Omitting 18 | .Ar operation 19 | prints a brief status message for the 20 | .Ar device 21 | (see 22 | .Sx Devices ) 23 | if the driver supports it. Otherwise, perform 24 | .Ar operation 25 | (see 26 | .Sx Operations ) 27 | on 28 | .Ar device 29 | attached to 30 | .Ar bus 31 | .Ss Options 32 | .Bl -tag 33 | .It Fl h 34 | Print usage message and exit. 35 | .It Fl v 36 | Print version information and exit. 37 | .El 38 | .Ss Buses 39 | Denoted by their sysfs names as listed in 40 | .Pa /sys/class/mdio_bus/ . 41 | As these are typically very awkward to type, 42 | .Xr glob 3 43 | patterns may be used to abbreviate them (see 44 | .Sx EXAMPLES ) 45 | .Ss Devices 46 | Multiple types of devices are supported via pluggable 47 | drivers. Different devices will use different addressing schemes. If 48 | you are unsure about which type of device you have, using the 49 | .Cm phy 50 | driver is probably a good bet if your PHY runs at speeds \(<= 1 Gbps. If your PHY is capable of speeds > 1 51 | Gbps, you will most likely want to use the 52 | .Cm mmd 53 | driver. 54 | .Pp 55 | The following devices are supported: 56 | .Bl -tag -offset 2n 57 | .It Cm phy Ar PHYAD 58 | Clause 22 (MDIO) PHY using address 59 | .Ar PHYAD . 60 | .Pp 61 | .Bl -tag -compact 62 | .It Ar PHYAD 63 | := 0-31 64 | .It Ar REG 65 | := 0-31 66 | .El 67 | .It Cm mmd Ar PRTAD:DEVAD 68 | Clause 45 (XMDIO) MMD using address 69 | .Ar PRTAD:DEVAD . 70 | .Pp 71 | .Bl -tag -compact 72 | .It Ar PRTAD 73 | := 0-31 74 | .It Ar DEVAD 75 | := 0-31 76 | .It Ar REG 77 | := 0-0xffff 78 | .El 79 | .It Cm mmd-c22 Ar PRTAD:DEVAD 80 | Clause 45 (XMDIO) MMD, accessed over Clause 22 registers 13 and 14, 81 | using address 82 | .Ar PRTAD:DEVAD . 83 | .Pp 84 | .Bl -tag -compact 85 | .It Ar PRTAD 86 | := 0-31 87 | .It Ar DEVAD 88 | := 0-31 89 | .It Ar REG 90 | := 0-0xffff 91 | .El 92 | .It Cm mva Ar PHYAD 93 | Marvell Alaska (mv88e1xxx) paged PHY. Register 22 is a paging register 94 | used to expand the register space. 95 | .Pp 96 | .Bl -tag -compact 97 | .It Ar PHYAD 98 | := 0-31 99 | .It Ar REG 100 | := PAGE:0-31 101 | .It Ar PAGE 102 | := 0-255|copper|fiber 103 | .El 104 | .It Cm mvls Ar ID 105 | Marvell LinkStreet (mv88e6xxx) Ethernet switch. If ID = 0, single-chip 106 | addressing is used; if ID \(!= 0, multi-chip addressing is used. 107 | .Pp 108 | .Bl -tag -compact 109 | .It Ar ID 110 | := 0-31 111 | .It Ar REG 112 | := PORT:0-31 113 | .It Ar PORT 114 | := 0-31|g1|g2 115 | .El 116 | .It Cm xrs Ar PHYAD 117 | Arrow/Flexibilis XRS700x Ethernet switch. Register space has a stride 118 | of 2, i.e. only even numbered registers are valid. 119 | .Pp 120 | .Bl -tag -compact 121 | .It Ar PHYAD 122 | := 0-31 123 | .It Ar REG 124 | := 0-0xffffffff 125 | .El 126 | .El 127 | .Ss Operations 128 | The following operations are supported: 129 | .Bl -tag -offset 2n 130 | .It Cm raw Ar REG Op Ar DATA[/MASK] 131 | Raw register access. Without 132 | .Ar DATA , 133 | .Ar REG 134 | is read. 135 | .Ar DATA 136 | without 137 | .Ar MASK 138 | will perform a single write to 139 | .Ar REG . 140 | .Ar DATA 141 | with 142 | .Ar MASK 143 | will perform a read/mask/write 144 | sequence. 145 | .Pp 146 | .Bl -tag -compact 147 | .It Ar REG 148 | Determined by the device type (see 149 | .Sx Devices ) 150 | .It Ar DATA 151 | := 0-0xffff 152 | .It Ar MASK 153 | := 0-0xffff 154 | .El 155 | .It Cm bench Ar REG Op Ar DATA 156 | Benchmark read performance. 157 | If 158 | .Ar DATA 159 | is supplied, it is written to 160 | .Ar REG , 161 | otherwise the current value in 162 | .Ar REG 163 | is read. 164 | .Ar REG 165 | is then read 1000 times. Any unexpected values are reported, along 166 | with the total time. 167 | .Pp 168 | .Bl -tag -compact 169 | .It Ar REG 170 | Determined by the device type (see 171 | .Sx Devices ) 172 | .It Ar DATA 173 | := 0-0xffff 174 | .El 175 | .El 176 | .Sh EXAMPLES 177 | .Pp 178 | Show all available buses: 179 | .Bd -literal -offset 2n 180 | ~# mdio 181 | 30be0000.ethernet-1 182 | fixed-0 183 | .Ed 184 | .Pp 185 | List all Clause 22 addressable devices on a bus (using 186 | .Xr glob 3 187 | pattern to abbreviate bus name): 188 | .Bd -literal -offset 2n 189 | ~# mdio 3* 190 | DEV PHY-ID LINK 191 | 0x01 0x01410dd0 up 192 | .Ed 193 | .Pp 194 | Read register 2 from PHY 1: 195 | .Bd -literal -offset 2n 196 | ~# mdio 3* phy 1 raw 2 197 | 0x0141 198 | .Ed 199 | .Pp 200 | Perform a reset on PHY 1: 201 | .Bd -literal -offset 2n 202 | ~# mdio 3* phy 1 raw 0 0x8000/0x7fff 203 | .Ed 204 | .Pp 205 | Read register 0x1000 from MMD 4 on PHY 9: 206 | .Bd -literal -offset 2n 207 | ~# mdio 3* mmd 9:4 raw 0x1000 208 | 0x2040 209 | .Ed 210 | .Pp 211 | Read status register from the copper page of an Alaska PHY: 212 | .Bd -literal -offset 2n 213 | ~# mdio 3* mva 1 raw copper:1 214 | 0x796d 215 | .Ed 216 | .Pp 217 | Set the device number, of LinkStreet switch 4, to 10: 218 | .Bd -literal -offset 2n 219 | ~# mdio 3* mvls 4 raw g1:28 0xa/0xfff0 220 | .Ed 221 | .Sh SEE ALSO 222 | .Xr mvls 8 223 | .Xr mdio-netlink 9 224 | .Sh STANDARDS 225 | IEEE Std 802.3-2018 226 | .Bl -bullet -compact 227 | .It 228 | Clause 22 (MDIO) 229 | .It 230 | Clause 45 (XMDIO) 231 | .El 232 | .Sh AUTHORS 233 | .An Tobias Waldekranz Aq Mt tobias@waldekranz.com 234 | .Sh CAVEATS 235 | In addition to the userspace parts of 236 | .Nm , 237 | the 238 | .Xr mdio-netlink 9 239 | kernel module must also be installed. 240 | 241 | -------------------------------------------------------------------------------- /man/mvls.8: -------------------------------------------------------------------------------- 1 | .Dd September 16, 2021 2 | .Dt MVLS 8 SMM 3 | .Os Linux 4 | .Sh NAME 5 | .Nm mvls 6 | .Nd Dump internal state of Marvell LinkStreet Ethernet switches 7 | .Sh SYNOPSIS 8 | .Nm 9 | .Op Cm port | Cm atu | Cm vtu | Cm pvt Op Ar DEV 10 | .Sh DESCRIPTION 11 | Without arguments, 12 | .Nm 13 | will dump the 14 | .Cm vtu , Cm atu , and Cm port 15 | tables. 16 | .Bl -tag -width 0 17 | .It Cm port 18 | List basic information about all ports on all switches on the system. 19 | .It Cm atu 20 | Dump all entries in the Address Translation Units (ATUs) of all 21 | devices on the system. 22 | .Qd ATU 23 | is a Marvell LinkStreet specific name for a Forwarding Database (FDB). 24 | .It Cm vtu 25 | Dump all entries in the VLAN Translation Units (VTUs) of all devices 26 | on the system. Each port will be in one of four states: 27 | .Bl -tag 28 | .It Cm . 29 | Not a member of the VLAN 30 | .It Cm u 31 | Member of the VLAN, traffic egresses untagged. 32 | .It Cm t 33 | Member of the VLAN, traffic egresses tagged. 34 | .It Cm = 35 | Member of the VLAN, traffic egresses unmodified. 36 | .El 37 | .It Cm pvt Op Ar DEV 38 | Without 39 | .Ar DEV , 40 | show a condensed view of the Port based VLAN Tables (PVTs) of all 41 | devices on the system. The output is an N \(mu N matrix, where N is 42 | the number of ports on the system. Each element will contain one of 43 | four possible values: 44 | .Bl -tag 45 | .It Cm . 46 | No communication is allowed between the ports at the corresponding row 47 | and column. 48 | .It Cm x 49 | Bidirectional communication is allowed between the ports at the 50 | corresponding row and column. 51 | .It Cm < 52 | Only unidirectional communication from the column port to the row port 53 | is allowed. 54 | .It Cm ^ 55 | Only unidirectional communication from the row port to the column port 56 | is allowed. 57 | .El 58 | .Pp 59 | Supplying 60 | .Ar DEV 61 | will dump the full PVT table for that device. This is useful to 62 | inspect entries that do not correspond to any physical device, 63 | i.e. those used by the mv88e6xxx driver to isolate Linux bridges. 64 | .El 65 | .Sh SEE ALSO 66 | .Xr devlink 8 67 | .Xr mdio 8 68 | .Sh HISTORY 69 | .Nm 70 | acquires its data from the devlink interface of the 71 | .Nm mv88e6xxx 72 | driver. In contrast to 73 | .Xr devlink 8 , 74 | it does not concern itself with maintaining multiple snapshots of 75 | register regions and whatnot \(en it is indended to be used 76 | interactively during development and debugging. 77 | .Sh AUTHORS 78 | .An Tobias Waldekranz Aq Mt tobias@waldekranz.com 79 | -------------------------------------------------------------------------------- /src/Makefile.am: -------------------------------------------------------------------------------- 1 | SUBDIRS = mdio mvls 2 | -------------------------------------------------------------------------------- /src/mdio/.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | .deps/ 3 | mdio 4 | -------------------------------------------------------------------------------- /src/mdio/Makefile.am: -------------------------------------------------------------------------------- 1 | sbin_PROGRAMS = mdio 2 | 3 | mdio_SOURCES = bus.c main.c mdio.c mdio.h mva.c mvls.c phy.c print_phy.c xrs.c 4 | mdio_CFLAGS = -Wall -Wextra -Werror -Wno-unused-parameter -I $(top_srcdir)/include $(mnl_CFLAGS) 5 | mdio_LDFLAGS = -T $(top_srcdir)/src/mdio/cmds.ld 6 | mdio_LDADD = $(mnl_LIBS) 7 | -------------------------------------------------------------------------------- /src/mdio/bus.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "mdio.h" 7 | 8 | static int bus_status_cb(uint32_t *data, int len, int err, void *_null) 9 | { 10 | uint16_t dev; 11 | 12 | if (len != MDIO_DEV_MAX * 3) 13 | return 1; 14 | 15 | printf("\e[7m%4s %10s %4s\e[0m\n", "DEV", "PHY-ID", "LINK"); 16 | for (dev = 0; dev < MDIO_DEV_MAX; dev++, data += 3) { 17 | if (data[1] == 0xffff && data[2] == 0xffff) 18 | continue; 19 | 20 | printf("0x%2.2x 0x%8.8x %s\n", dev, 21 | (data[1] << 16) | data[2], 22 | (data[0] & BMSR_LSTATUS) ? "up" : "down"); 23 | } 24 | 25 | return err; 26 | } 27 | 28 | int bus_status(const char *bus) 29 | { 30 | struct mdio_nl_insn insns[] = { 31 | INSN(ADD, IMM(0), IMM(0), REG(1)), 32 | 33 | INSN(READ, REG(1), IMM(1), REG(0)), 34 | INSN(EMIT, REG(0), 0, 0), 35 | INSN(READ, REG(1), IMM(2), REG(0)), 36 | INSN(EMIT, REG(0), 0, 0), 37 | INSN(READ, REG(1), IMM(3), REG(0)), 38 | INSN(EMIT, REG(0), 0, 0), 39 | 40 | INSN(ADD, REG(1), IMM(1), REG(1)), 41 | INSN(JNE, REG(1), IMM(MDIO_DEV_MAX), IMM(-8)), 42 | }; 43 | struct mdio_prog prog = MDIO_PROG_FIXED(insns); 44 | int err; 45 | 46 | err = mdio_xfer(bus, &prog, bus_status_cb, NULL); 47 | if (err) { 48 | fprintf(stderr, "ERROR: Unable to read status (%d)\n", err); 49 | return 1; 50 | } 51 | 52 | return 0; 53 | } 54 | 55 | static int bus_list_cb(const char *bus, void *_null) 56 | { 57 | puts(bus); 58 | return 0; 59 | } 60 | 61 | int bus_list(void) 62 | { 63 | mdio_for_each("*", bus_list_cb, NULL); 64 | return 0; 65 | } 66 | -------------------------------------------------------------------------------- /src/mdio/cmds.ld: -------------------------------------------------------------------------------- 1 | SECTIONS 2 | { 3 | .cmds : { 4 | PROVIDE(cmds_start = .); 5 | *(.cmd_registry) 6 | PROVIDE(cmds_end = .); 7 | } 8 | } 9 | INSERT AFTER .data; 10 | -------------------------------------------------------------------------------- /src/mdio/main.c: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "mdio.h" 9 | 10 | int usage(int rc, FILE *fp) 11 | { 12 | fputs("SYNOPSIS\n" 13 | " mdio -- List available buses\n" 14 | " mdio BUS -- Probe BUS for active devices\n" 15 | " mdio BUS OBJ -- Show status of OBJ\n" 16 | " mdio BUS OBJ OP -- Perform OP on OBJ\n" 17 | "\n" 18 | "OPTIONS\n" 19 | " -h This help text\n" 20 | " -v Show verision and contact information\n" 21 | "\n" 22 | "Bus names may be abbreviated using glob(3) syntax, i.e. \"fixed*\"\n" 23 | "would typically match against \"fixed-0\".\n" 24 | "\n" 25 | "OBJECTS\n" 26 | " phy PHYAD\n" 27 | " Clause 22 (MDIO) PHY using address PHYAD.\n" 28 | "\n" 29 | " REG: u5\n" 30 | "\n" 31 | " mmd PRTAD[:DEVAD]\n" 32 | " Clause 45 (XMDIO) PHY using address PRTAD:DEVAD.\n" 33 | "\n" 34 | " mmd-c22 PRTAD[:DEVAD]\n" 35 | " Clause 45 (XMDIO) PHY addressed over Clause 22 using address\n" 36 | " PRTAD:DEVAD.\n" 37 | "\n" 38 | " REG: u16\n" 39 | "\n" 40 | " mva PHYAD\n" 41 | " Operate on Marvell Alaska (mv88e8xxx) PHY using address PHYAD.\n" 42 | " Register 22 is assumed to be the page register.\n" 43 | "\n" 44 | " REG: u8|\"copper\"|\"fiber\":u5\n" 45 | "\n" 46 | " mvls ID\n" 47 | " Operate on Marvell LinkStreet (mv88e6xxx) device attached to BUS\n" 48 | " using address ID. If ID is 0, single-chip addressing is used; all\n" 49 | " other IDs use multi-chip addressing.\n" 50 | "\n" 51 | " REG: u5|\"global1\"|\"global2\" u5\n" 52 | "\n" 53 | " xrs PHYAD\n" 54 | " Operate of Arrow/Flexibilis XRS700x device using address PHYAD.\n" 55 | "\n" 56 | " REG: u32 (Stride of 2, only even registers are valid)\n" 57 | "\n" 58 | "OPERATIONS\n" 59 | " raw REG [DATA[/MASK]]\n" 60 | " Raw register access. Without DATA, REG is read. An unmasked DATA will\n" 61 | " do a single write to REG. DATA with MASK will run the atomic sequence \n" 62 | " write(REG, (read(REG) & MASK) | DATA)\n" 63 | " sequence.\n" 64 | "\n" 65 | " DATA: u16\n" 66 | " MASK: u16\n" 67 | "\n" 68 | " bench REG [DATA]\n" 69 | " Benchmark read performance. If DATA is supplied, it is written to REG,\n" 70 | " otherwise the current value in REG is read. REG is then read 1000\n" 71 | " times. Any unexpected values are reported, along with the total time.\n" 72 | "\n" 73 | " DATA: u16\n" 74 | "\n" 75 | "EXAMPLES\n" 76 | " Show all available buses:\n" 77 | " ~# mdio\n" 78 | " 30be0000.ethernet-1\n" 79 | " fixed-0\n" 80 | "\n" 81 | " List all Clause 22 addressable devices on a bus (using glob(3) pattern\n" 82 | " to abbreviate bus name):\n" 83 | " ~# mdio 3*\n" 84 | " DEV PHY-ID LINK\n" 85 | " 0x01 0x01410dd0 up\n" 86 | "\n" 87 | " Read register 2 from PHY 1:\n" 88 | " ~# mdio 3* phy 1 raw 2\n" 89 | " 0x0141\n" 90 | "\n" 91 | " Perform a reset on PHY 1:\n" 92 | " ~# mdio 3* phy 1 raw 0 0x8000/0x7fff\n" 93 | "\n" 94 | " Read register 0x1000 from MMD 4 on PHY 9:\n" 95 | " ~# mdio 3* mmd 9:4 raw 0x1000\n" 96 | " 0x2040\n" 97 | "\n" 98 | " Read status register from the copper page of an Alaska PHY:\n" 99 | " ~# mdio 3* mva 1 raw copper:1\n" 100 | " 0x796d\n" 101 | "\n" 102 | " Set the device number, of LinkStreet switch 4, to 10:\n" 103 | " ~# mdio 3* mvls 4 raw g1:28 0xa/0xfff0\n" 104 | , fp); 105 | 106 | return rc; 107 | } 108 | 109 | int version(void) 110 | { 111 | puts("v" PACKAGE_VERSION); 112 | puts("\nBug report address: " PACKAGE_BUGREPORT); 113 | 114 | return 0; 115 | } 116 | 117 | int bus_list_cb(const char *bus, void *_null) 118 | { 119 | puts(bus); 120 | return 0; 121 | } 122 | 123 | int main(int argc, char **argv) 124 | { 125 | struct cmd *cmd; 126 | char *arg, *bus; 127 | int opt; 128 | 129 | while ((opt = getopt(argc, argv, "hv")) != -1) { 130 | switch (opt) { 131 | case 'h': 132 | return usage(0, stdout); 133 | case 'v': 134 | return version(); 135 | default: 136 | return usage(1, stderr); 137 | } 138 | } 139 | 140 | argv += optind; 141 | argc -= optind; 142 | 143 | if (mdio_init()) { 144 | if (mdio_modprobe()) { 145 | fprintf(stderr, "ERROR: mdio-netlink module not " 146 | "detected, and could not be loaded.\n"); 147 | return 1; 148 | } 149 | 150 | if (mdio_init()) { 151 | fprintf(stderr, "ERROR: Unable to initialize.\n"); 152 | return 1; 153 | } 154 | } 155 | 156 | arg = argv_pop(&argc, &argv); 157 | if (!arg) 158 | return bus_list() ? 1 : 0; 159 | 160 | if (mdio_parse_bus(arg, &bus)) { 161 | fprintf(stderr, "ERROR: \"%s\" does not match any known bus.\n", 162 | arg); 163 | return 1; 164 | } 165 | 166 | arg = argv_peek(argc, argv); 167 | if (!arg) 168 | return bus_status(bus) ? 1 : 0; 169 | 170 | for (cmd = &cmds_start; cmd < &cmds_end; cmd++) { 171 | if (!strcmp(cmd->name, arg)) { 172 | argv_pop(&argc, &argv); 173 | return cmd->exec(bus, argc, argv) ? 1 : 0; 174 | } 175 | } 176 | 177 | /* Allow the driver name to be omitted in the common phy/mmd 178 | * case. */ 179 | if (strchr(arg, ':')) 180 | return mmd_exec(bus, argc, argv) ? 1 : 0; 181 | else 182 | return phy_exec(bus, argc, argv) ? 1 : 0; 183 | } 184 | -------------------------------------------------------------------------------- /src/mdio/mdio.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "mdio.h" 18 | 19 | static char buf[0x1000] __attribute__ ((aligned (NLMSG_ALIGNTO))); 20 | static const size_t len = 0x1000; 21 | static uint16_t mdio_family; 22 | 23 | static int parse_attrs(const struct nlattr *attr, void *data) 24 | { 25 | const struct nlattr **tb = data; 26 | int type = mnl_attr_get_type(attr); 27 | 28 | tb[type] = attr; 29 | 30 | return MNL_CB_OK; 31 | } 32 | 33 | static struct mnl_socket *msg_send(int bus, struct nlmsghdr *nlh) 34 | { 35 | struct mnl_socket *nl; 36 | int ret; 37 | 38 | nl = mnl_socket_open(bus); 39 | if (nl == NULL) { 40 | perror("mnl_socket_open"); 41 | return NULL; 42 | } 43 | 44 | ret = mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID); 45 | if (ret < 0) { 46 | perror("mnl_socket_bind"); 47 | return NULL; 48 | } 49 | 50 | ret = mnl_socket_sendto(nl, nlh, nlh->nlmsg_len); 51 | if (ret < 0) { 52 | perror("mnl_socket_send"); 53 | return NULL; 54 | } 55 | 56 | return nl; 57 | } 58 | 59 | static int msg_recv(struct mnl_socket *nl, mnl_cb_t callback, void *data, int seq) 60 | { 61 | unsigned int portid; 62 | int ret; 63 | 64 | portid = mnl_socket_get_portid(nl); 65 | 66 | ret = mnl_socket_recvfrom(nl, buf, len); 67 | while (ret > 0) { 68 | ret = mnl_cb_run(buf, ret, seq, portid, callback, data); 69 | if (ret <= 0) 70 | break; 71 | ret = mnl_socket_recvfrom(nl, buf, len); 72 | } 73 | 74 | return ret; 75 | } 76 | 77 | static int msg_query(struct nlmsghdr *nlh, mnl_cb_t callback, void *data) 78 | { 79 | unsigned int seq; 80 | struct mnl_socket *nl; 81 | int ret; 82 | 83 | seq = time(NULL); 84 | nlh->nlmsg_seq = seq; 85 | 86 | nl = msg_send(NETLINK_GENERIC, nlh); 87 | if (!nl) 88 | return -ENOTSUP; 89 | 90 | ret = msg_recv(nl, callback, data, seq); 91 | mnl_socket_close(nl); 92 | return ret; 93 | } 94 | 95 | 96 | struct nlmsghdr *msg_init(int cmd, int flags) 97 | { 98 | struct genlmsghdr *genl; 99 | struct nlmsghdr *nlh; 100 | 101 | nlh = mnl_nlmsg_put_header(buf); 102 | if (!nlh) 103 | return NULL; 104 | 105 | nlh->nlmsg_type = mdio_family; 106 | nlh->nlmsg_flags = flags; 107 | 108 | genl = mnl_nlmsg_put_extra_header(nlh, sizeof(struct genlmsghdr)); 109 | genl->cmd = cmd; 110 | genl->version = 1; 111 | 112 | return nlh; 113 | } 114 | 115 | static int mdio_parse_bus_cb(const char *bus, void *_id) 116 | { 117 | char **id = _id; 118 | 119 | *id = strdup(bus); 120 | return 1; 121 | } 122 | 123 | int mdio_parse_bus(const char *str, char **bus) 124 | { 125 | *bus = NULL; 126 | mdio_for_each(str, mdio_parse_bus_cb, bus); 127 | 128 | if (*bus) 129 | return 0; 130 | 131 | fprintf(stderr, "ERROR: \"%s\" does not match any known MDIO bus\n", str); 132 | return ENODEV; 133 | } 134 | 135 | int mdio_parse_dev(const char *str, uint16_t *dev, bool allow_c45) 136 | { 137 | unsigned long p, d = 0; 138 | char *end; 139 | 140 | p = strtoul(str, &end, 0); 141 | if (!end[0]) { 142 | allow_c45 = false; 143 | goto c22; 144 | } 145 | 146 | if (end[0] != ':') 147 | /* not clause 45 either */ 148 | goto err_invalid; 149 | 150 | if (!allow_c45) { 151 | fprintf(stderr, "ERROR: Clause-45 addressing not allowed\n"); 152 | return EINVAL; 153 | } 154 | 155 | d = strtoul(end + 1, &end, 0); 156 | if (end[0]) 157 | goto err_invalid; 158 | 159 | if (allow_c45 && (d > 31)) { 160 | fprintf(stderr, "ERROR: Device %lu is out of range [0-31]\n", d); 161 | return ERANGE; 162 | } 163 | 164 | c22: 165 | if (p > 31) { 166 | fprintf(stderr, "ERROR: %s %lu is out of range [0-31]\n", 167 | allow_c45 ? "Port" : "Device", d); 168 | return ERANGE; 169 | } 170 | 171 | *dev = allow_c45 ? mdio_phy_id_c45(p, d) : p; 172 | return 0; 173 | 174 | err_invalid: 175 | fprintf(stderr, "ERROR: \"%s\" is not a valid device\n", str); 176 | return EINVAL; 177 | } 178 | 179 | void mdio_prog_push(struct mdio_prog *prog, struct mdio_nl_insn insn) 180 | { 181 | prog->insns = realloc(prog->insns, (++prog->len) * sizeof(insn)); 182 | memcpy(&prog->insns[prog->len - 1], &insn, sizeof(insn)); 183 | } 184 | 185 | int mdio_device_dflt_parse_reg(struct mdio_device *dev, 186 | int *argcp, char ***argvp, 187 | uint32_t *regs, uint32_t *rege) 188 | { 189 | unsigned long long rs, re, base = 0; 190 | char *arg, *str, *end; 191 | 192 | errno = 0; 193 | 194 | arg = argv_pop(argcp, argvp); 195 | if (!arg) { 196 | fprintf(stderr, "ERROR: Expected register\n"); 197 | return EINVAL; 198 | } 199 | 200 | str = arg; 201 | rs = strtoull(str, &end, 0); 202 | if (errno) 203 | goto inval; 204 | 205 | switch (*end) { 206 | case '\0': 207 | re = rs; 208 | break; 209 | case '+': 210 | base = rs; 211 | /* fallthrough */ 212 | case '-': 213 | if (!rege) { 214 | fprintf(stderr, "ERROR: Unexpected register range\n"); 215 | return EINVAL; 216 | } 217 | 218 | str = end + 1; 219 | re = strtoull(str, &end, 0); 220 | if (errno) { 221 | fprintf(stderr, "ERROR: \"%s\" is not a valid register range\n", arg); 222 | return EINVAL; 223 | } 224 | 225 | re += base; 226 | break; 227 | default: 228 | inval: 229 | fprintf(stderr, "ERROR: \"%s\" is not a valid register\n", arg); 230 | return EINVAL; 231 | } 232 | 233 | if (rs > dev->mem.max) { 234 | fprintf(stderr, "ERROR: Register %llu is out of range " 235 | "[0-%"PRIu32"]\n", rs, dev->mem.max); 236 | return ERANGE; 237 | } 238 | 239 | if (re > dev->mem.max) { 240 | fprintf(stderr, "ERROR: Register %llu is out of range " 241 | "[0-%"PRIu32"]\n", re, dev->mem.max); 242 | return ERANGE; 243 | } 244 | 245 | *regs = rs; 246 | 247 | if (rege) 248 | *rege = re; 249 | 250 | return 0; 251 | } 252 | 253 | int mdio_device_parse_reg(struct mdio_device *dev, int *argcp, char ***argvp, 254 | uint32_t *regs, uint32_t *rege) 255 | { 256 | if (dev->driver->parse_reg) 257 | return dev->driver->parse_reg(dev, argcp, argvp, regs, rege); 258 | 259 | return mdio_device_dflt_parse_reg(dev, argcp, argvp, regs, rege); 260 | } 261 | 262 | int mdio_device_dflt_parse_val(struct mdio_device *dev, 263 | int *argcp, char ***argvp, 264 | uint32_t *val, uint32_t *mask) 265 | { 266 | unsigned long long max = (1 << dev->mem.width) - 1; 267 | unsigned long long v, m = 0; 268 | char *str, *end; 269 | 270 | if (dev->driver->parse_val) 271 | return dev->driver->parse_val(dev, argcp, argvp, val, mask); 272 | 273 | str = argv_pop(argcp, argvp); 274 | if (!str) { 275 | fprintf(stderr, "ERROR: Expected register\n"); 276 | return EINVAL; 277 | } 278 | 279 | v = strtoull(str, &end, 0); 280 | if (!end[0]) 281 | goto done; 282 | 283 | if (end[0] != '/') 284 | goto err_invalid; 285 | 286 | if (!mask) { 287 | fprintf(stderr, "ERROR: Masking of value not allowed\n"); 288 | return EINVAL; 289 | } 290 | 291 | m = strtoull(end + 1, &end, 0); 292 | if (end[0]) 293 | goto err_invalid; 294 | 295 | done: 296 | if (v > max) { 297 | fprintf(stderr, "ERROR: Value %#llx is out of range " 298 | "[0-%#llx]", v, max); 299 | return ERANGE; 300 | } 301 | 302 | if (m > max) { 303 | fprintf(stderr, "ERROR: Mask %#llx is out of range " 304 | "[0-%#llx]", m, max); 305 | return ERANGE; 306 | } 307 | 308 | *val = v; 309 | if (mask) 310 | *mask = m; 311 | return 0; 312 | 313 | err_invalid: 314 | fprintf(stderr, "ERROR: \"%s\" is not a valid register value\n", str); 315 | return EINVAL; 316 | } 317 | 318 | int mdio_device_parse_val(struct mdio_device *dev, int *argcp, char ***argvp, 319 | uint32_t *val, uint32_t *mask) 320 | { 321 | if (dev->driver->parse_val) 322 | return dev->driver->parse_val(dev, argcp, argvp, val, mask); 323 | 324 | return mdio_device_dflt_parse_val(dev, argcp, argvp, val, mask); 325 | } 326 | 327 | int mdio_common_raw_read_cb(uint32_t *data, int len, int err, void *_null) 328 | { 329 | if (len != 1) 330 | return 1; 331 | 332 | printf("0x%4.4x\n", *data); 333 | return err; 334 | } 335 | 336 | int mdio_common_raw_write_cb(uint32_t *data, int len, int err, void *_null) 337 | { 338 | if (len != 0) 339 | return 1; 340 | 341 | return err; 342 | } 343 | 344 | int mdio_common_raw_exec(struct mdio_device *dev, int argc, char **argv) 345 | { 346 | struct mdio_prog prog = MDIO_PROG_EMPTY; 347 | mdio_xfer_cb_t cb = mdio_common_raw_read_cb; 348 | uint32_t reg, val, mask; 349 | int err; 350 | 351 | err = mdio_device_parse_reg(dev, &argc, &argv, ®, NULL); 352 | if (err) 353 | return err; 354 | 355 | if (argv_peek(argc, argv)) { 356 | cb = mdio_common_raw_write_cb; 357 | 358 | err = mdio_device_parse_val(dev, &argc, &argv, &val, &mask); 359 | if (err) 360 | return err; 361 | } 362 | 363 | if (argv_peek(argc, argv)) { 364 | fprintf(stderr, "ERROR: Unexpected argument\n"); 365 | return EINVAL; 366 | } 367 | 368 | if ((cb == mdio_common_raw_write_cb) && mask) { 369 | err = dev->driver->read(dev, &prog, reg); 370 | if (err) 371 | return err; 372 | mdio_prog_push(&prog, INSN(AND, REG(0), IMM(mask), REG(0))); 373 | mdio_prog_push(&prog, INSN(OR, REG(0), IMM(val), REG(0))); 374 | err = dev->driver->write(dev, &prog, reg, REG(0)); 375 | if (err) 376 | return err; 377 | } else if (cb == mdio_common_raw_write_cb) { 378 | err = dev->driver->write(dev, &prog, reg, IMM(val)); 379 | if (err) 380 | return err; 381 | } else { 382 | err = dev->driver->read(dev, &prog, reg); 383 | if (err) 384 | return err; 385 | mdio_prog_push(&prog, INSN(EMIT, REG(0), 0, 0)); 386 | } 387 | 388 | err = mdio_xfer(dev->bus, &prog, cb, NULL); 389 | free(prog.insns); 390 | if (err) { 391 | fprintf(stderr, "ERROR: Raw operation failed (%d)\n", err); 392 | return 1; 393 | } 394 | 395 | return 0; 396 | } 397 | 398 | int mdio_common_bench_cb(uint32_t *data, int len, int err, void *_start) 399 | { 400 | struct timespec end, *start = _start; 401 | 402 | clock_gettime(CLOCK_MONOTONIC, &end); 403 | 404 | if (len) { 405 | int i; 406 | 407 | printf("Read back %d incorrect values:\n", len); 408 | err = 1; 409 | 410 | for (i = 0; i < len; i++) 411 | printf("\t0x%4.4x\n", data[i]); 412 | } 413 | 414 | end.tv_sec -= start->tv_sec; 415 | end.tv_nsec -= start->tv_nsec; 416 | if (end.tv_nsec < 0) { 417 | end.tv_nsec += 1000000000; 418 | end.tv_sec--; 419 | } 420 | 421 | if (err) 422 | printf("Benchmark failed after "); 423 | else 424 | printf("Performed 1000 reads in "); 425 | 426 | if (end.tv_sec) 427 | printf("%"PRId64".%2.2"PRId64"s\n", (int64_t)end.tv_sec, (int64_t)end.tv_nsec / 10000000); 428 | else if (end.tv_nsec > 1000000) 429 | printf("%"PRId64"ms\n", (int64_t)end.tv_nsec / 1000000); 430 | else if (end.tv_nsec > 1000) 431 | printf("%"PRId64"us\n", (int64_t)end.tv_nsec / 1000); 432 | else 433 | printf("%"PRId64"ns\n", (int64_t)end.tv_nsec); 434 | 435 | return err; 436 | } 437 | 438 | int mdio_common_bench_exec(struct mdio_device *dev, int argc, char **argv) 439 | { 440 | struct mdio_prog prog = MDIO_PROG_EMPTY; 441 | struct timespec start; 442 | uint32_t reg, val = 0; 443 | int err, loop; 444 | 445 | err = mdio_device_parse_reg(dev, &argc, &argv, ®, NULL); 446 | if (err) 447 | return err; 448 | 449 | if (argv_peek(argc, argv)) { 450 | err = mdio_device_parse_val(dev, &argc, &argv, &val, NULL); 451 | if (err) 452 | return err; 453 | 454 | mdio_prog_push(&prog, INSN(ADD, IMM(val), IMM(0), REG(7))); 455 | err = dev->driver->write(dev, &prog, reg, REG(7)); 456 | if (err) 457 | return err; 458 | } else { 459 | err = dev->driver->read(dev, &prog, reg); 460 | if (err) 461 | return err; 462 | 463 | mdio_prog_push(&prog, INSN(ADD, REG(0), IMM(0), REG(7))); 464 | } 465 | 466 | if (argv_peek(argc, argv)) { 467 | fprintf(stderr, "ERROR: Unexpected argument"); 468 | return EINVAL; 469 | } 470 | 471 | 472 | mdio_prog_push(&prog, INSN(ADD, IMM(0), IMM(0), REG(6))); 473 | 474 | loop = prog.len; 475 | 476 | err = dev->driver->read(dev, &prog, reg); 477 | if (err) 478 | return err; 479 | 480 | mdio_prog_push(&prog, INSN(JEQ, REG(0), REG(7), IMM(1))); 481 | mdio_prog_push(&prog, INSN(EMIT, REG(0), 0, 0)); 482 | mdio_prog_push(&prog, INSN(ADD, REG(6), IMM(1), REG(6))); 483 | mdio_prog_push(&prog, INSN(JNE, REG(6), IMM(1000), GOTO(prog.len, loop))); 484 | 485 | clock_gettime(CLOCK_MONOTONIC, &start); 486 | err = mdio_xfer_timeout(dev->bus, &prog, mdio_common_bench_cb, &start, 10000); 487 | free(prog.insns); 488 | if (err) { 489 | fprintf(stderr, "ERROR: Bench operation failed (%d)\n", err); 490 | return 1; 491 | } 492 | 493 | return 0; 494 | } 495 | 496 | struct reg_range { 497 | uint32_t start; 498 | uint32_t end; 499 | }; 500 | 501 | int mdio_common_dump_cb(uint32_t *data, int len, int err, void *_range) 502 | { 503 | struct reg_range *range = _range; 504 | uint32_t reg; 505 | 506 | if (len != (int)(range->end - range->start + 1)) 507 | return 1; 508 | 509 | for (reg = range->start; reg <= range->end; reg++) 510 | printf("0x%04x: 0x%04x\n", reg, *data++); 511 | 512 | return err; 513 | } 514 | 515 | int mdio_common_dump_exec_one(struct mdio_device *dev, int *argc, char ***argv) 516 | { 517 | struct mdio_prog prog = MDIO_PROG_EMPTY; 518 | struct reg_range range; 519 | uint32_t reg; 520 | int err; 521 | 522 | err = mdio_device_parse_reg(dev, argc, argv, &range.start, &range.end); 523 | if (err) 524 | return err; 525 | 526 | /* Can't emit a loop, since there's no way to pass the (mdio) 527 | * register in a (mdio-netlink) register - so we unroll it. */ 528 | for (reg = range.start; reg <= range.end; reg++) { 529 | err = dev->driver->read(dev, &prog, reg); 530 | if (err) 531 | return err; 532 | 533 | mdio_prog_push(&prog, INSN(EMIT, REG(0), 0, 0)); 534 | } 535 | 536 | err = mdio_xfer_timeout(dev->bus, &prog, mdio_common_dump_cb, &range, 10000); 537 | free(prog.insns); 538 | if (err) { 539 | fprintf(stderr, "ERROR: Dump operation failed (%d)\n", err); 540 | return 1; 541 | } 542 | 543 | return 0; 544 | } 545 | 546 | int mdio_common_dump_exec(struct mdio_device *dev, int argc, char **argv) 547 | { 548 | int err; 549 | 550 | while (argv_peek(argc, argv)) { 551 | err = mdio_common_dump_exec_one(dev, &argc, &argv); 552 | if (err) 553 | return err; 554 | } 555 | 556 | return 0; 557 | } 558 | 559 | int mdio_common_exec(struct mdio_device *dev, int argc, char **argv) 560 | { 561 | if (!argc) 562 | return 1; 563 | 564 | if (!strcmp(argv[0], "raw")) { 565 | argv_pop(&argc, &argv); 566 | return mdio_common_raw_exec(dev, argc, argv); 567 | } else if (!strcmp(argv[0], "bench")) { 568 | argv_pop(&argc, &argv); 569 | return mdio_common_bench_exec(dev, argc, argv); 570 | } else if (!strcmp(argv[0], "dump")) { 571 | argv_pop(&argc, &argv); 572 | return mdio_common_dump_exec(dev, argc, argv); 573 | } 574 | 575 | return mdio_common_raw_exec(dev, argc, argv); 576 | } 577 | 578 | 579 | 580 | struct mdio_xfer_data { 581 | mdio_xfer_cb_t cb; 582 | void *arg; 583 | int err; 584 | }; 585 | 586 | static int mdio_xfer_cb(const struct nlmsghdr *nlh, void *_xfer) 587 | { 588 | struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh); 589 | struct nlattr *tb[MDIO_NLA_MAX + 1] = {}; 590 | struct mdio_xfer_data *xfer = _xfer; 591 | uint32_t *data; 592 | int len, err; 593 | 594 | mnl_attr_parse(nlh, sizeof(*genl), parse_attrs, tb); 595 | 596 | if (tb[MDIO_NLA_ERROR]) 597 | xfer->err = (int)mnl_attr_get_u32(tb[MDIO_NLA_ERROR]); 598 | 599 | if (!tb[MDIO_NLA_DATA]) 600 | return MNL_CB_ERROR; 601 | 602 | len = mnl_attr_get_payload_len(tb[MDIO_NLA_DATA]) / sizeof(uint32_t); 603 | data = mnl_attr_get_payload(tb[MDIO_NLA_DATA]); 604 | 605 | err = xfer->cb(data, len, xfer->err, xfer->arg); 606 | return err ? MNL_CB_ERROR : MNL_CB_OK; 607 | } 608 | 609 | int mdio_xfer_timeout(const char *bus, struct mdio_prog *prog, 610 | mdio_xfer_cb_t cb, void *arg, uint16_t timeout_ms) 611 | { 612 | struct mdio_xfer_data xfer = { .cb = cb, .arg = arg }; 613 | struct nlmsghdr *nlh; 614 | int err; 615 | 616 | nlh = msg_init(MDIO_GENL_XFER, NLM_F_REQUEST | NLM_F_ACK); 617 | if (!nlh) 618 | return -ENOMEM; 619 | 620 | mnl_attr_put_strz(nlh, MDIO_NLA_BUS_ID, bus); 621 | mnl_attr_put(nlh, MDIO_NLA_PROG, prog->len * sizeof(*prog->insns), 622 | prog->insns); 623 | 624 | mnl_attr_put_u16(nlh, MDIO_NLA_TIMEOUT, timeout_ms); 625 | 626 | err = msg_query(nlh, mdio_xfer_cb, &xfer); 627 | return xfer.err ? : err; 628 | } 629 | 630 | int mdio_xfer(const char *bus, struct mdio_prog *prog, 631 | mdio_xfer_cb_t cb, void *arg) 632 | { 633 | return mdio_xfer_timeout(bus, prog, cb, arg, 1000); 634 | } 635 | 636 | int mdio_for_each(const char *match, 637 | int (*cb)(const char *bus, void *arg), void *arg) 638 | { 639 | char gmatch[0x80]; 640 | glob_t gl; 641 | size_t i; 642 | int err; 643 | 644 | snprintf(gmatch, sizeof(gmatch), "/sys/class/mdio_bus/%s", match); 645 | glob(gmatch, 0, NULL, &gl); 646 | 647 | for (err = 0, i = 0; i < gl.gl_pathc; i++) { 648 | err = cb(&gl.gl_pathv[i][strlen("/sys/class/mdio_bus/")], arg); 649 | if (err) 650 | break; 651 | } 652 | 653 | globfree(&gl); 654 | return err; 655 | } 656 | 657 | 658 | static int family_id_cb(const struct nlmsghdr *nlh, void *_null) 659 | { 660 | struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh); 661 | struct nlattr *tb[CTRL_ATTR_MAX + 1] = {}; 662 | 663 | mnl_attr_parse(nlh, sizeof(*genl), parse_attrs, tb); 664 | if (!tb[CTRL_ATTR_FAMILY_ID]) 665 | return MNL_CB_ERROR; 666 | 667 | mdio_family = mnl_attr_get_u16(tb[CTRL_ATTR_FAMILY_ID]); 668 | return MNL_CB_OK; 669 | } 670 | 671 | int mdio_modprobe(void) 672 | { 673 | int wstatus; 674 | pid_t pid; 675 | 676 | pid = fork(); 677 | if (pid < 0) { 678 | return -errno; 679 | } else if (!pid) { 680 | execl("/sbin/modprobe", "modprobe", "mdio-netlink", NULL); 681 | exit(1); 682 | } 683 | 684 | if (waitpid(pid, &wstatus, 0) <= 0) 685 | return -ECHILD; 686 | 687 | if (WIFEXITED(wstatus) && !WEXITSTATUS(wstatus)) 688 | return 0; 689 | 690 | return -EPERM; 691 | } 692 | 693 | int mdio_init(void) 694 | { 695 | struct genlmsghdr *genl; 696 | struct nlmsghdr *nlh; 697 | 698 | nlh = mnl_nlmsg_put_header(buf); 699 | nlh->nlmsg_type = GENL_ID_CTRL; 700 | nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; 701 | 702 | genl = mnl_nlmsg_put_extra_header(nlh, sizeof(struct genlmsghdr)); 703 | genl->cmd = CTRL_CMD_GETFAMILY; 704 | genl->version = 1; 705 | 706 | mnl_attr_put_u16(nlh, CTRL_ATTR_FAMILY_ID, GENL_ID_CTRL); 707 | mnl_attr_put_strz(nlh, CTRL_ATTR_FAMILY_NAME, "mdio"); 708 | 709 | return msg_query(nlh, family_id_cb, NULL); 710 | } 711 | -------------------------------------------------------------------------------- /src/mdio/mdio.h: -------------------------------------------------------------------------------- 1 | #ifndef _LIBMDIO_H 2 | #define _LIBMDIO_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define BIT(_n) (1 << (_n)) 10 | 11 | #define ARRAY_SIZE(_a) (sizeof(_a) / sizeof((_a)[0])) 12 | #define container_of(ptr, type, member) ({ \ 13 | const typeof( ((type *)0)->member ) \ 14 | *__mptr = (ptr); \ 15 | (type *)( (char *)__mptr - __builtin_offsetof(type,member) );}) 16 | 17 | #define MDIO_DEV_MAX 32 18 | 19 | #define REG(_r) ((MDIO_NL_ARG_REG << 16) | ((uint16_t)(_r))) 20 | #define IMM(_n) ((MDIO_NL_ARG_IMM << 16) | ((uint16_t)(_n))) 21 | #define INVALID 0 22 | #define GOTO(_from, _to) ((MDIO_NL_ARG_IMM << 16) | ((uint16_t)((_to) - (_from) - 1))) 23 | 24 | #define INSN(_op, _a0, _a1, _a2) \ 25 | ((struct mdio_nl_insn) \ 26 | { \ 27 | .op = MDIO_NL_OP_ ## _op, \ 28 | .arg0 = _a0, \ 29 | .arg1 = _a1, \ 30 | .arg2 = _a2 \ 31 | }) 32 | 33 | static inline char *argv_peek(int argc, char **argv) 34 | { 35 | if (argc <= 0) 36 | return NULL; 37 | 38 | return argv[0]; 39 | } 40 | 41 | static inline char *argv_pop(int *argcp, char ***argvp) 42 | { 43 | char *arg = argv_peek(*argcp, *argvp); 44 | 45 | if (!arg) 46 | return NULL; 47 | 48 | (*argcp)--; 49 | (*argvp)++; 50 | return arg; 51 | } 52 | 53 | struct cmd { 54 | const char *name; 55 | int (*exec)(const char *bus, int argc, char **argv); 56 | }; 57 | 58 | // Derived from https://stackoverflow.com/a/4152185 59 | #define DEFINE_CMD(_name, _exec) \ 60 | __attribute__((used, section(".cmd_registry"), aligned(__alignof__(struct cmd)) )) \ 61 | struct cmd _exec ## _cmd = { .name = _name, .exec = _exec } 62 | 63 | extern struct cmd cmds_start; 64 | extern struct cmd cmds_end; 65 | 66 | void print_phy_bmcr (uint16_t val); 67 | void print_phy_bmsr (uint16_t val); 68 | void print_phy_id (uint16_t id_hi, uint16_t id_lo); 69 | void print_phy_estatus(uint16_t val); 70 | 71 | void print_mmd_devid(uint16_t id_hi, uint16_t id_lo); 72 | void print_mmd_pkgid(uint16_t id_hi, uint16_t id_lo); 73 | void print_mmd_devs (uint16_t devs_hi, uint16_t devs_lo); 74 | 75 | struct mmd_print_device { 76 | void (*print_ctrl1)(uint16_t val); 77 | void (*print_stat1)(uint16_t val); 78 | void (*print_speed)(uint16_t val); 79 | void (*print_extra)(uint32_t *data); 80 | }; 81 | 82 | extern const struct mmd_print_device pma_print_device; 83 | extern const struct mmd_print_device pcs_print_device; 84 | extern const struct mmd_print_device an_print_device; 85 | 86 | int mdio_parse_bus(const char *str, char **bus); 87 | int mdio_parse_dev(const char *str, uint16_t *dev, bool allow_c45); 88 | 89 | struct mdio_prog { 90 | struct mdio_nl_insn *insns; 91 | int len; 92 | }; 93 | #define MDIO_PROG_EMPTY ((struct mdio_prog) { 0 }) 94 | #define MDIO_PROG_FIXED(_insns) \ 95 | ((struct mdio_prog) \ 96 | { \ 97 | .insns = _insns, \ 98 | .len = ARRAY_SIZE(_insns) \ 99 | }) 100 | 101 | void mdio_prog_push(struct mdio_prog *prog, struct mdio_nl_insn insn); 102 | 103 | typedef int (*mdio_xfer_cb_t)(uint32_t *data, int len, int err, void *arg); 104 | 105 | struct mdio_ops { 106 | char *bus; 107 | 108 | void (*usage)(FILE *fp); 109 | 110 | int (*push_read) (struct mdio_ops *ops, struct mdio_prog *prog, 111 | uint16_t dev, uint16_t reg); 112 | int (*push_write)(struct mdio_ops *ops, struct mdio_prog *prog, 113 | uint16_t dev, uint16_t reg, uint32_t val); 114 | }; 115 | 116 | int mdio_raw_exec (struct mdio_ops *ops, int argc, char **argv); 117 | 118 | int mdio_xfer_timeout(const char *bus, struct mdio_prog *prog, 119 | mdio_xfer_cb_t cb, void *arg, uint16_t timeout_ms); 120 | int mdio_xfer(const char *bus, struct mdio_prog *prog, 121 | mdio_xfer_cb_t cb, void *arg); 122 | 123 | int mdio_for_each(const char *match, 124 | int (*cb)(const char *bus, void *arg), void *arg); 125 | int mdio_modprobe(void); 126 | int mdio_init(void); 127 | 128 | struct mdio_driver; 129 | 130 | struct mdio_device { 131 | const struct mdio_driver *driver; 132 | const char *bus; 133 | 134 | struct { 135 | uint32_t max; 136 | uint8_t stride; 137 | uint8_t width; 138 | } mem; 139 | }; 140 | 141 | struct mdio_driver { 142 | /* Mandatory */ 143 | int (*read) (struct mdio_device *dev, struct mdio_prog *prog, 144 | uint32_t reg); 145 | int (*write)(struct mdio_device *dev, struct mdio_prog *prog, 146 | uint32_t reg, uint32_t val); 147 | 148 | /* Optional */ 149 | int (*parse_reg)(struct mdio_device *dev, int *argcp, char ***argvp, 150 | uint32_t *regs, uint32_t *rege); 151 | int (*parse_val)(struct mdio_device *dev, int *argcp, char ***argvp, 152 | uint32_t *val, uint32_t *mask); 153 | }; 154 | 155 | int mdio_common_exec(struct mdio_device *dev, int argc, char **argv); 156 | 157 | int bus_status(const char *bus); 158 | int bus_list(void); 159 | 160 | int phy_exec(const char *bus, int argc, char **argv); 161 | int mmd_exec(const char *bus, int argc, char **argv); 162 | 163 | #endif /* _LIBMDIO_H */ 164 | -------------------------------------------------------------------------------- /src/mdio/mva.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "mdio.h" 8 | 9 | #define MVA_PAGE 0x16 10 | #define MVA_PAGE_COPPER 0 11 | #define MVA_PAGE_FIBER 1 12 | 13 | struct mva_device { 14 | struct mdio_device dev; 15 | uint16_t id; 16 | }; 17 | 18 | int mva_read(struct mdio_device *dev, struct mdio_prog *prog, uint32_t reg) 19 | { 20 | struct mva_device *pdev = (void *)dev; 21 | uint8_t page; 22 | 23 | page = reg >> 16; 24 | reg &= 0x1f; 25 | 26 | /* Save current page in R1 and write the requested one, if they differ. */ 27 | mdio_prog_push(prog, INSN(READ, IMM(pdev->id), IMM(MVA_PAGE), REG(1))); 28 | mdio_prog_push(prog, INSN(JEQ, REG(1), IMM(page), IMM(1))); 29 | mdio_prog_push(prog, INSN(WRITE, IMM(pdev->id), IMM(MVA_PAGE), IMM(page))); 30 | 31 | mdio_prog_push(prog, INSN(READ, IMM(pdev->id), IMM(reg), REG(0))); 32 | 33 | /* Restore old page if we changed it. */ 34 | mdio_prog_push(prog, INSN(JEQ, REG(1), IMM(page), IMM(1))); 35 | mdio_prog_push(prog, INSN(WRITE, IMM(pdev->id), IMM(MVA_PAGE), REG(1))); 36 | return 0; 37 | } 38 | 39 | int mva_write(struct mdio_device *dev, struct mdio_prog *prog, 40 | uint32_t reg, uint32_t val) 41 | { 42 | struct mva_device *pdev = (void *)dev; 43 | uint8_t page; 44 | 45 | page = reg >> 16; 46 | reg &= 0x1f; 47 | 48 | /* Save current page in R1 and write the requested one, if they differ. */ 49 | mdio_prog_push(prog, INSN(READ, IMM(pdev->id), IMM(MVA_PAGE), REG(1))); 50 | mdio_prog_push(prog, INSN(JEQ, REG(1), IMM(page), IMM(1))); 51 | mdio_prog_push(prog, INSN(WRITE, IMM(pdev->id), IMM(MVA_PAGE), IMM(page))); 52 | 53 | mdio_prog_push(prog, INSN(WRITE, IMM(pdev->id), IMM(reg), val)); 54 | 55 | /* Restore old page if we changed it. */ 56 | mdio_prog_push(prog, INSN(JEQ, REG(1), IMM(page), IMM(1))); 57 | mdio_prog_push(prog, INSN(WRITE, IMM(pdev->id), IMM(MVA_PAGE), REG(1))); 58 | return 0; 59 | } 60 | 61 | static int mva_parse_reg(struct mdio_device *dev, int *argcp, char ***argvp, 62 | uint32_t *regs, uint32_t *rege) 63 | { 64 | char *str, *tok, *end; 65 | unsigned long r; 66 | uint8_t page; 67 | 68 | if (rege) { 69 | fprintf(stderr, "ERROR: Implement ranges\n"); 70 | return ENOSYS; 71 | } 72 | 73 | str = argv_pop(argcp, argvp); 74 | tok = str ? strtok(str, ":") : NULL; 75 | if (!tok) { 76 | fprintf(stderr, "ERROR: PAGE:REG\n"); 77 | return EINVAL; 78 | } else if (!strcmp(tok, "copper") || !strcmp(tok, "cu")) { 79 | page = MVA_PAGE_COPPER; 80 | } else if (!strcmp(tok, "fiber") || !strcmp(tok, "fibre")) { 81 | page = MVA_PAGE_FIBER; 82 | } else { 83 | r = strtoul(tok, &end, 0); 84 | if (*end) { 85 | fprintf(stderr, "ERROR: \"%s\" is not a valid page\n", tok); 86 | return EINVAL; 87 | } 88 | 89 | if (r > 255) { 90 | fprintf(stderr, "ERROR: page %lu is out of range [0-255]\n", r); 91 | return EINVAL; 92 | } 93 | 94 | page = r; 95 | } 96 | 97 | tok = strtok(NULL, ":"); 98 | if (!tok) { 99 | fprintf(stderr, "ERROR: Expected REG\n"); 100 | return EINVAL; 101 | } 102 | 103 | r = strtoul(tok, &end, 0); 104 | if (*end) { 105 | fprintf(stderr, "ERROR: \"%s\" is not a valid register\n", 106 | tok); 107 | return EINVAL; 108 | } 109 | 110 | if (r > 31) { 111 | fprintf(stderr, "ERROR: register %lu is out of range [0-31]\n", r); 112 | return EINVAL; 113 | } 114 | 115 | *regs = (page << 16) | r; 116 | return 0; 117 | } 118 | 119 | static const struct mdio_driver mva_driver = { 120 | .read = mva_read, 121 | .write = mva_write, 122 | 123 | .parse_reg = mva_parse_reg, 124 | }; 125 | 126 | int mva_status_cb(uint32_t *data, int len, int err, void *_null) 127 | { 128 | if (len != 5) 129 | return 1; 130 | 131 | if (data[2] == 0xffff && data[3] == 0xffff) { 132 | fprintf(stderr, "No device found\n"); 133 | return 1; 134 | } 135 | 136 | print_phy_bmcr(data[0]); 137 | putchar('\n'); 138 | print_phy_bmsr(data[1]); 139 | putchar('\n'); 140 | print_phy_id(data[2], data[3]); 141 | 142 | printf("Current page: %u\n", data[4] & 0xff); 143 | return err; 144 | } 145 | 146 | int mva_exec_status(struct mva_device *pdev, int argc, char **argv) 147 | { 148 | struct mdio_nl_insn insns[] = { 149 | INSN(READ, IMM(pdev->id), IMM(0), REG(0)), 150 | INSN(EMIT, REG(0), 0, 0), 151 | INSN(READ, IMM(pdev->id), IMM(1), REG(0)), 152 | INSN(EMIT, REG(0), 0, 0), 153 | INSN(READ, IMM(pdev->id), IMM(2), REG(0)), 154 | INSN(EMIT, REG(0), 0, 0), 155 | INSN(READ, IMM(pdev->id), IMM(3), REG(0)), 156 | INSN(EMIT, REG(0), 0, 0), 157 | INSN(READ, IMM(pdev->id), IMM(MVA_PAGE), REG(0)), 158 | INSN(EMIT, REG(0), 0, 0), 159 | }; 160 | struct mdio_prog prog = MDIO_PROG_FIXED(insns); 161 | int err; 162 | 163 | err = mdio_xfer(pdev->dev.bus, &prog, mva_status_cb, NULL); 164 | if (err) { 165 | fprintf(stderr, "ERROR: Unable to read status (%d)\n", err); 166 | return 1; 167 | } 168 | 169 | return 0; 170 | } 171 | 172 | int mva_exec(const char *bus, int argc, char **argv) 173 | { 174 | struct mva_device pdev = { 175 | .dev = { 176 | .bus = bus, 177 | .driver = &mva_driver, 178 | 179 | .mem = { 180 | .stride = 1, 181 | .width = 16, 182 | }, 183 | }, 184 | }; 185 | char *arg; 186 | 187 | arg = argv_pop(&argc, &argv); 188 | if (!arg || mdio_parse_dev(arg, &pdev.id, true)) 189 | return 1; 190 | 191 | arg = argv_peek(argc, argv); 192 | if (!arg || !strcmp(arg, "status")) 193 | return mva_exec_status(&pdev, argc, argv); 194 | 195 | return mdio_common_exec(&pdev.dev, argc, argv); 196 | } 197 | DEFINE_CMD("mva", mva_exec); 198 | -------------------------------------------------------------------------------- /src/mdio/mvls.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "mdio.h" 9 | 10 | #define MVLS_CMD 0 11 | #define MVLS_CMD_BUSY BIT(15) 12 | #define MVLS_CMD_C22 BIT(12) 13 | 14 | #define MVLS_DATA 1 15 | 16 | #define MVLS_G1 0x1b 17 | #define MVLS_G2 0x1c 18 | 19 | #define MVLS_REG(_port, _reg) (((_port) << 16) | (_reg)) 20 | 21 | enum mvls_famliy { 22 | FAM_UNKNOWN, 23 | FAM_SPINNAKER, 24 | FAM_OPAL, 25 | FAM_AGATE, 26 | FAM_PERIDOT, 27 | FAM_AMETHYST, 28 | }; 29 | 30 | struct mvls_device { 31 | struct mdio_device dev; 32 | uint16_t id; 33 | 34 | }; 35 | 36 | static inline uint16_t mvls_multi_cmd(uint8_t port, uint8_t reg, bool write) 37 | { 38 | return MVLS_CMD_BUSY | MVLS_CMD_C22 | ((write ? 1 : 2) << 10)| 39 | (port << 5) | reg; 40 | } 41 | 42 | static void mvls_wait_cmd(struct mdio_prog *prog, uint8_t id) 43 | { 44 | int retry; 45 | 46 | retry = prog->len; 47 | mdio_prog_push(prog, INSN(READ, IMM(id), IMM(MVLS_CMD), REG(0))); 48 | mdio_prog_push(prog, INSN(AND, REG(0), IMM(MVLS_CMD_BUSY), REG(0))); 49 | mdio_prog_push(prog, INSN(JEQ, REG(0), IMM(MVLS_CMD_BUSY), 50 | GOTO(prog->len, retry))); 51 | } 52 | 53 | static void mvls_read_to(struct mdio_device *dev, struct mdio_prog *prog, 54 | uint32_t reg, uint8_t to) 55 | { 56 | struct mvls_device *mdev = (void *)dev; 57 | uint16_t port; 58 | 59 | port = reg >> 16; 60 | reg &= 0xffff; 61 | 62 | if (!mdev->id) { 63 | /* Single-chip addressing, the switch uses the entire 64 | * underlying bus */ 65 | mdio_prog_push(prog, INSN(READ, IMM(port), IMM(reg), REG(to))); 66 | return; 67 | } 68 | 69 | mdio_prog_push(prog, INSN(WRITE, IMM(mdev->id), IMM(MVLS_CMD), 70 | IMM(mvls_multi_cmd(port, reg, false)))); 71 | mvls_wait_cmd(prog, mdev->id); 72 | mdio_prog_push(prog, INSN(READ, IMM(mdev->id), IMM(MVLS_DATA), REG(to))); 73 | } 74 | 75 | static int mvls_read(struct mdio_device *dev, struct mdio_prog *prog, 76 | uint32_t reg) 77 | { 78 | mvls_read_to(dev, prog, reg, 0); 79 | return 0; 80 | } 81 | 82 | static int mvls_write(struct mdio_device *dev, struct mdio_prog *prog, 83 | uint32_t reg, uint32_t val) 84 | { 85 | struct mvls_device *mdev = (void *)dev; 86 | uint16_t port; 87 | 88 | port = reg >> 16; 89 | reg &= 0xffff; 90 | 91 | if (!mdev->id) { 92 | /* Single-chip addressing, the switch uses the entire 93 | * underlying bus */ 94 | mdio_prog_push(prog, INSN(WRITE, IMM(port), IMM(reg), val)); 95 | return 0; 96 | } 97 | 98 | mdio_prog_push(prog, INSN(WRITE, IMM(mdev->id), IMM(MVLS_DATA), val)); 99 | mdio_prog_push(prog, INSN(WRITE, IMM(mdev->id), IMM(MVLS_CMD), 100 | IMM(mvls_multi_cmd(port, reg, true)))); 101 | mvls_wait_cmd(prog, mdev->id); 102 | return 0; 103 | } 104 | 105 | static void mvls_wait(struct mdio_device *dev, struct mdio_prog *prog, 106 | uint32_t reg) 107 | { 108 | int retry = prog->len; 109 | 110 | mvls_read_to(dev, prog, reg, 0); 111 | mdio_prog_push(prog, INSN(AND, REG(0), IMM(MVLS_CMD_BUSY), REG(0))); 112 | mdio_prog_push(prog, INSN(JEQ, REG(0), IMM(MVLS_CMD_BUSY), 113 | GOTO(prog->len, retry))); 114 | } 115 | 116 | int mvls_id_cb(uint32_t *data, int len, int err, void *_id) 117 | { 118 | uint32_t *id = _id; 119 | 120 | if (len != 1) 121 | return 1; 122 | 123 | *id = *data; 124 | return err; 125 | } 126 | 127 | static uint32_t mvls_id_exec(struct mdio_device *dev) 128 | { 129 | struct mdio_prog prog = MDIO_PROG_EMPTY; 130 | uint32_t id; 131 | int err; 132 | 133 | mvls_read(dev, &prog, MVLS_REG(0x10, 0x03)); 134 | mdio_prog_push(&prog, INSN(EMIT, REG(0), 0, 0)); 135 | 136 | err = mdio_xfer(dev->bus, &prog, mvls_id_cb, &id); 137 | free(prog.insns); 138 | if (err) { 139 | fprintf(stderr, "ERROR: ID operation failed (%d)\n", err); 140 | return 0; 141 | } 142 | 143 | return id; 144 | } 145 | 146 | static enum mvls_famliy mvls_get_family(struct mdio_device *dev) 147 | { 148 | uint32_t id = mvls_id_exec(dev); 149 | 150 | switch (id >> 4) { 151 | case 0x099: 152 | return FAM_OPAL; 153 | case 0x352: 154 | return FAM_AGATE; 155 | case 0x0a1: 156 | return FAM_PERIDOT; 157 | default: 158 | break; 159 | } 160 | 161 | return FAM_UNKNOWN; 162 | } 163 | 164 | static void mvls_print_portvec(uint16_t portvec) 165 | { 166 | int i; 167 | 168 | for (i = 0; i < 11; i++) { 169 | if (portvec & (1 << i)) 170 | printf(" %x", i); 171 | else 172 | fputs(" .", stdout); 173 | } 174 | 175 | } 176 | 177 | int mvls_lag_cb(uint32_t *data, int len, int err, void *_null) 178 | { 179 | int mask, lag; 180 | 181 | if (len != 16 + 8) 182 | return 1; 183 | 184 | puts("\e[7m LAG 0 1 2 3 4 5 6 7 8 9 a\e[0m"); 185 | for (lag = 0; lag < 16; lag++) { 186 | if (!(data[lag] & 0x7ff)) 187 | continue; 188 | 189 | printf("%4d", lag); 190 | mvls_print_portvec(data[lag]); 191 | putchar('\n'); 192 | } 193 | 194 | putchar('\n'); 195 | data += 16; 196 | 197 | puts("\e[7mMASK 0 1 2 3 4 5 6 7 8 9 a\e[0m"); 198 | for (mask = 0; mask < 8; mask++) { 199 | printf("%4d", mask); 200 | mvls_print_portvec(data[mask]); 201 | putchar('\n'); 202 | } 203 | return err; 204 | } 205 | 206 | static int mvls_lag_exec(struct mdio_device *dev, int argc, char **argv) 207 | { 208 | struct mdio_prog prog = MDIO_PROG_EMPTY; 209 | char *arg; 210 | int err, i; 211 | 212 | /* Drop "lag" token. */ 213 | argv_pop(&argc, &argv); 214 | 215 | arg = argv_pop(&argc, &argv); 216 | if (arg) { 217 | fprintf(stderr, "ERROR: Unknown LAG command\n"); 218 | return 1; 219 | } 220 | 221 | for (i = 0; i < 16; i++) { 222 | mvls_write(dev, &prog, MVLS_REG(MVLS_G2, 0x08), IMM(i << 11)); 223 | mvls_read(dev, &prog, MVLS_REG(MVLS_G2, 0x08)); 224 | mdio_prog_push(&prog, INSN(EMIT, REG(0), 0, 0)); 225 | } 226 | 227 | for (i = 0; i < 8; i++) { 228 | mvls_read(dev, &prog, MVLS_REG(MVLS_G2, 0x07)); 229 | 230 | /* Keep the current value of the HashTrunk bit when 231 | * selecting the mask to read out. */ 232 | mdio_prog_push(&prog, INSN(AND, REG(0), IMM(1 << 11), REG(0))); 233 | mdio_prog_push(&prog, INSN(OR, REG(0), IMM(i << 12), REG(0))); 234 | 235 | mvls_write(dev, &prog, MVLS_REG(MVLS_G2, 0x07), REG(0)); 236 | mvls_read(dev, &prog, MVLS_REG(MVLS_G2, 0x07)); 237 | mdio_prog_push(&prog, INSN(EMIT, REG(0), 0, 0)); 238 | } 239 | 240 | err = mdio_xfer(dev->bus, &prog, mvls_lag_cb, NULL); 241 | free(prog.insns); 242 | if (err) { 243 | fprintf(stderr, "ERROR: LAG operation failed (%d)\n", err); 244 | return 1; 245 | } 246 | 247 | return 0; 248 | } 249 | 250 | struct mvls_counter_ctx { 251 | enum mvls_famliy fam; 252 | 253 | uint32_t prev[11][6]; 254 | }; 255 | 256 | int mvls_counter_cb(uint32_t *data, int len, int err, void *_ctx) 257 | { 258 | struct mvls_counter_ctx *ctx = _ctx; 259 | uint32_t now[6]; 260 | int i, n; 261 | 262 | if (len != 11 * 6 * 2) 263 | return 1; 264 | 265 | printf(" \e[7m Bcasts Ucasts Mcasts\e[0m\n"); 266 | printf("\e[7mP Rx Tx Rx Tx Rx Tx\e[0m\n"); 267 | 268 | for (i = 0; i < 11; i++, data += 6 * 2) { 269 | for (n = 0; n < 6; n++) 270 | now[n] = (data[2 * n] << 16) | data[2 * n + 1]; 271 | 272 | if (!memcmp(ctx->prev[i], now, sizeof(now))) 273 | continue; 274 | 275 | printf("%x %3u %3u %3u %3u %3u %3u\n", i, 276 | now[1] - ctx->prev[i][1], now[4] - ctx->prev[i][4], 277 | now[0] - ctx->prev[i][0], now[3] - ctx->prev[i][3], 278 | now[2] - ctx->prev[i][2], now[5] - ctx->prev[i][5]); 279 | 280 | memcpy(ctx->prev[i], now, sizeof(now)); 281 | } 282 | return err; 283 | } 284 | 285 | static void mvls_counter_read_one(struct mdio_device *dev, struct mdio_prog *prog, 286 | uint8_t counter) 287 | { 288 | mvls_write(dev, prog, MVLS_REG(MVLS_G1, 0x1d), IMM((1 << 15) | (4 << 12) | counter)); 289 | mvls_wait(dev, prog, MVLS_REG(MVLS_G1, 0x1d)); 290 | 291 | mvls_read(dev, prog, MVLS_REG(MVLS_G1, 0x1e)); 292 | mdio_prog_push(prog, INSN(EMIT, REG(0), 0, 0)); 293 | mvls_read(dev, prog, MVLS_REG(MVLS_G1, 0x1f)); 294 | mdio_prog_push(prog, INSN(EMIT, REG(0), 0, 0)); 295 | 296 | } 297 | 298 | static int mvls_counter_exec(struct mdio_device *dev, int argc, char **argv) 299 | { 300 | struct mdio_prog prog = MDIO_PROG_EMPTY; 301 | struct mvls_counter_ctx ctx = {}; 302 | int err, base, shift, loop; 303 | bool repeat = false; 304 | char *arg; 305 | 306 | /* Drop "counter" token. */ 307 | argv_pop(&argc, &argv); 308 | 309 | arg = argv_pop(&argc, &argv); 310 | if (arg) { 311 | if (!strcmp(arg, "repeat")) 312 | repeat = true; 313 | else { 314 | fprintf(stderr, "ERROR: Unexpected counter command\n"); 315 | return 1; 316 | } 317 | } 318 | 319 | ctx.fam = mvls_get_family(dev); 320 | switch (ctx.fam) { 321 | case FAM_AGATE: 322 | case FAM_PERIDOT: 323 | case FAM_AMETHYST: 324 | base = 1 << 5; 325 | shift = 1 << 5; 326 | break; 327 | default: 328 | base = 0; 329 | shift = 1; 330 | } 331 | 332 | mvls_wait(dev, &prog, MVLS_REG(MVLS_G1, 0x1d)); 333 | 334 | mdio_prog_push(&prog, INSN(ADD, IMM((1 << 15) | (5 << 12) | base), IMM(0), REG(1))); 335 | 336 | loop = prog.len; 337 | 338 | mvls_write(dev, &prog, MVLS_REG(MVLS_G1, 0x1d), REG(1)); 339 | mvls_wait(dev, &prog, MVLS_REG(MVLS_G1, 0x1d)); 340 | 341 | mvls_counter_read_one(dev, &prog, 0x04); 342 | mvls_counter_read_one(dev, &prog, 0x06); 343 | mvls_counter_read_one(dev, &prog, 0x07); 344 | 345 | mvls_counter_read_one(dev, &prog, 0x10); 346 | mvls_counter_read_one(dev, &prog, 0x13); 347 | mvls_counter_read_one(dev, &prog, 0x12); 348 | 349 | mdio_prog_push(&prog, INSN(ADD, REG(1), IMM(shift), REG(1))); 350 | mdio_prog_push(&prog, INSN(JNE, REG(1), 351 | IMM((1 << 15) | (5 << 12) | (base + (shift * 11))), 352 | GOTO(prog.len, loop))); 353 | 354 | while (!(err = mdio_xfer(dev->bus, &prog, mvls_counter_cb, &ctx))) { 355 | if (repeat) { 356 | sleep(1); 357 | fputs("\e[2J", stdout); 358 | } else { 359 | break; 360 | } 361 | } 362 | 363 | free(prog.insns); 364 | if (err) { 365 | fprintf(stderr, "ERROR: COUNTER operation failed (%d)\n", err); 366 | return 1; 367 | } 368 | 369 | return 0; 370 | } 371 | 372 | int mvls_atu_cb(uint32_t *data, int len, int err, void *_null) 373 | { 374 | if (len != 0) 375 | return 1; 376 | 377 | return err; 378 | } 379 | 380 | static int mvls_atu_exec(struct mdio_device *dev, int argc, char **argv) 381 | { 382 | struct mdio_prog prog = MDIO_PROG_EMPTY; 383 | uint8_t op = 0; 384 | char *arg; 385 | int err; 386 | 387 | /* Drop "atu" token. */ 388 | argv_pop(&argc, &argv); 389 | 390 | arg = argv_pop(&argc, &argv); 391 | if (!arg) { 392 | fprintf(stderr, "ERROR: Expected ATU command\n"); 393 | return 1; 394 | } else if (!strcmp(arg, "flush")) { 395 | arg = argv_pop(&argc, &argv); 396 | if (!arg) { 397 | op += 2; 398 | goto exec; 399 | } else if (!strcmp(arg, "all")) { 400 | goto read_stat; 401 | } else { 402 | long fid = strtol(arg, NULL, 0); 403 | 404 | if (fid < 0) { 405 | fprintf(stderr, "ERROR: Invalid FID \"%s\"\n", arg); 406 | return 1; 407 | } 408 | 409 | /* Limit to specific FID */ 410 | mvls_read_to(dev, &prog, MVLS_REG(MVLS_G1, 0x01), 0); 411 | mdio_prog_push(&prog, INSN(AND, REG(0), IMM(0xf0000), REG(0))); 412 | mdio_prog_push(&prog, INSN(OR, REG(0), IMM(fid & 0xfff), REG(0))); 413 | mvls_write(dev, &prog, MVLS_REG(MVLS_G1, 0x01), REG(0)); 414 | op = 4; 415 | } 416 | 417 | read_stat: 418 | arg = argv_pop(&argc, &argv); 419 | if (!arg) { 420 | op += 2; 421 | } else if (!strcmp(arg, "static")) { 422 | op += 1; 423 | } else { 424 | fprintf(stderr, "ERROR: Invalid option \"%s\"\n", arg); 425 | } 426 | } else { 427 | fprintf(stderr, "ERROR: Unknown ATU command \"%s\"\n", arg); 428 | return 1; 429 | } 430 | 431 | exec: 432 | mvls_wait(dev, &prog, MVLS_REG(MVLS_G1, 0x0b)); 433 | 434 | mvls_read_to(dev, &prog, MVLS_REG(MVLS_G1, 0x0b), 0); 435 | mdio_prog_push(&prog, INSN(AND, REG(0), IMM(0xfff), REG(0))); 436 | mdio_prog_push(&prog, INSN(OR, REG(0), IMM(BIT(15) | (op << 12)), REG(0))); 437 | mvls_write(dev, &prog, MVLS_REG(MVLS_G1, 0x0b), REG(0)); 438 | 439 | mvls_wait(dev, &prog, MVLS_REG(MVLS_G1, 0x0b)); 440 | 441 | err = mdio_xfer(dev->bus, &prog, mvls_atu_cb, NULL); 442 | free(prog.insns); 443 | if (err) { 444 | fprintf(stderr, "ERROR: ATU operation failed (%d)\n", err); 445 | return 1; 446 | } 447 | 448 | return 0; 449 | } 450 | 451 | static int mvls_parse_reg(struct mdio_device *dev, int *argcp, char ***argvp, 452 | uint32_t *regs, uint32_t *rege) 453 | { 454 | char *str, *tok, *end; 455 | unsigned long r; 456 | uint16_t port; 457 | 458 | if (rege) { 459 | fprintf(stderr, "ERROR: Implement ranges\n"); 460 | return ENOSYS; 461 | } 462 | 463 | str = argv_pop(argcp, argvp); 464 | tok = str ? strtok(str, ":") : NULL; 465 | if (!tok) { 466 | fprintf(stderr, "ERROR: Expected PORT:REG\n"); 467 | return EINVAL; 468 | } 469 | 470 | if (!strcmp(tok, "global1") || !strcmp(tok, "g1")) { 471 | port = MVLS_G1; 472 | } else if (!strcmp(tok, "global2") || !strcmp(tok, "g2")) { 473 | port = MVLS_G2; 474 | } else { 475 | r = strtoul(tok, &end, 0); 476 | if (*end) { 477 | fprintf(stderr, "ERROR: \"%s\" is not a valid port\n", tok); 478 | return EINVAL; 479 | } 480 | 481 | if (r > 31) { 482 | fprintf(stderr, "ERROR: Port %lu is out of range [0-31]\n", r); 483 | return EINVAL; 484 | } 485 | 486 | port = r; 487 | } 488 | 489 | tok = strtok(NULL, ":"); 490 | if (!tok) { 491 | fprintf(stderr, "ERROR: Expected REG\n"); 492 | return EINVAL; 493 | } 494 | 495 | r = strtoul(tok, &end, 0); 496 | if (*end) { 497 | fprintf(stderr, "ERROR: \"%s\" is not a valid register\n", 498 | tok); 499 | return EINVAL; 500 | } 501 | 502 | if (r > 31) { 503 | fprintf(stderr, "ERROR: register %lu is out of range [0-31]\n", r); 504 | return EINVAL; 505 | } 506 | 507 | *regs = (port << 16) | r; 508 | return 0; 509 | } 510 | 511 | struct mdio_driver mvls_driver = { 512 | .read = mvls_read, 513 | .write = mvls_write, 514 | 515 | .parse_reg = mvls_parse_reg, 516 | }; 517 | 518 | static int mvls_exec(const char *bus, int argc, char **argv) 519 | { 520 | struct mvls_device mdev = { 521 | .dev = { 522 | .bus = bus, 523 | .driver = &mvls_driver, 524 | 525 | .mem = { 526 | .stride = 1, 527 | .width = 16, 528 | }, 529 | }, 530 | }; 531 | char *arg; 532 | 533 | arg = argv_pop(&argc, &argv); 534 | if (!arg || mdio_parse_dev(arg, &mdev.id, true)) 535 | return 1; 536 | 537 | arg = argv_peek(argc, argv); 538 | if (!arg) 539 | return 1; 540 | 541 | if (!strcmp(arg, "atu")) 542 | return mvls_atu_exec(&mdev.dev, argc, argv); 543 | if (!strcmp(arg, "counter")) 544 | return mvls_counter_exec(&mdev.dev, argc, argv); 545 | if (!strcmp(arg, "lag")) 546 | return mvls_lag_exec(&mdev.dev, argc, argv); 547 | 548 | return mdio_common_exec(&mdev.dev, argc, argv); 549 | } 550 | DEFINE_CMD("mvls", mvls_exec); 551 | -------------------------------------------------------------------------------- /src/mdio/phy.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "mdio.h" 7 | #include "linux/bitfield.h" 8 | 9 | struct phy_device { 10 | struct mdio_device dev; 11 | uint16_t id; 12 | }; 13 | 14 | int phy_read(struct mdio_device *dev, struct mdio_prog *prog, uint32_t reg) 15 | { 16 | struct phy_device *pdev = (void *)dev; 17 | 18 | mdio_prog_push(prog, INSN(READ, IMM(pdev->id), IMM(reg), REG(0))); 19 | return 0; 20 | } 21 | 22 | int phy_write(struct mdio_device *dev, struct mdio_prog *prog, 23 | uint32_t reg, uint32_t val) 24 | { 25 | struct phy_device *pdev = (void *)dev; 26 | 27 | mdio_prog_push(prog, INSN(WRITE, IMM(pdev->id), IMM(reg), val)); 28 | return 0; 29 | } 30 | 31 | static const struct mdio_driver phy_driver = { 32 | .read = phy_read, 33 | .write = phy_write, 34 | }; 35 | 36 | int phy_status_cb(uint32_t *data, int len, int err, void *_null) 37 | { 38 | if (len != 5) 39 | return 1; 40 | 41 | if (data[2] == 0xffff && data[3] == 0xffff) { 42 | fprintf(stderr, "No device found\n"); 43 | return 1; 44 | } 45 | 46 | print_phy_bmcr(data[0]); 47 | putchar('\n'); 48 | print_phy_bmsr(data[1]); 49 | putchar('\n'); 50 | print_phy_id(data[2], data[3]); 51 | if (data[1] & BMSR_ESTATEN) { 52 | putchar('\n'); 53 | print_phy_estatus(data[4]); 54 | } 55 | 56 | return err; 57 | } 58 | 59 | int phy_exec_status(struct phy_device *pdev, int argc, char **argv) 60 | { 61 | struct mdio_nl_insn insns[] = { 62 | INSN(READ, IMM(pdev->id), IMM(0), REG(0)), 63 | INSN(EMIT, REG(0), 0, 0), 64 | INSN(READ, IMM(pdev->id), IMM(1), REG(0)), 65 | INSN(EMIT, REG(0), 0, 0), 66 | INSN(READ, IMM(pdev->id), IMM(2), REG(0)), 67 | INSN(EMIT, REG(0), 0, 0), 68 | INSN(READ, IMM(pdev->id), IMM(3), REG(0)), 69 | INSN(EMIT, REG(0), 0, 0), 70 | INSN(READ, IMM(pdev->id), IMM(15), REG(0)), 71 | INSN(EMIT, REG(0), 0, 0), 72 | }; 73 | struct mdio_prog prog = MDIO_PROG_FIXED(insns); 74 | int err; 75 | 76 | err = mdio_xfer(pdev->dev.bus, &prog, phy_status_cb, NULL); 77 | if (err) { 78 | fprintf(stderr, "ERROR: Unable to read status (%d)\n", err); 79 | return 1; 80 | } 81 | 82 | return 0; 83 | } 84 | 85 | int phy_exec(const char *bus, int argc, char **argv) 86 | { 87 | struct phy_device pdev = { 88 | .dev = { 89 | .bus = bus, 90 | .driver = &phy_driver, 91 | 92 | .mem = { 93 | .stride = 1, 94 | .width = 16, 95 | .max = 31, 96 | }, 97 | }, 98 | }; 99 | char *arg; 100 | 101 | arg = argv_pop(&argc, &argv); 102 | if (!arg || mdio_parse_dev(arg, &pdev.id, false)) 103 | return 1; 104 | 105 | arg = argv_peek(argc, argv); 106 | if (!arg || !strcmp(arg, "status")) 107 | return phy_exec_status(&pdev, argc, argv); 108 | 109 | return mdio_common_exec(&pdev.dev, argc, argv); 110 | } 111 | DEFINE_CMD("phy", phy_exec); 112 | 113 | int mmd_status_cb(uint32_t *data, int len, int err, void *arg) 114 | { 115 | const struct mmd_print_device *dev = arg; 116 | 117 | if (len != 16) 118 | return 1; 119 | 120 | if ((data[2] == 0xffff && data[3] == 0xffff) || 121 | (!data[2] && !data[3])) { 122 | fprintf(stderr, "No device found\n"); 123 | return 1; 124 | } 125 | 126 | if (dev->print_ctrl1) { 127 | dev->print_ctrl1(data[0]); 128 | putchar('\n'); 129 | } 130 | 131 | if (dev->print_stat1) { 132 | dev->print_stat1(data[1]); 133 | putchar('\n'); 134 | } 135 | 136 | print_mmd_devid(data[2], data[3]); 137 | putchar('\n'); 138 | 139 | if (dev->print_speed) { 140 | dev->print_speed(data[4]); 141 | putchar('\n'); 142 | } 143 | 144 | print_mmd_devs(data[6], data[5]); 145 | putchar('\n'); 146 | 147 | if (dev->print_extra) { 148 | dev->print_extra(data); 149 | putchar('\n'); 150 | } 151 | 152 | print_mmd_pkgid(data[14], data[15]); 153 | 154 | return err; 155 | } 156 | 157 | static const struct mmd_print_device null_print_device = { 0 }; 158 | 159 | int mmd_exec_status(struct phy_device *pdev, int argc, char **argv) 160 | { 161 | const struct mmd_print_device *dev; 162 | struct mdio_nl_insn insns[] = { 163 | INSN(ADD, IMM(0), IMM(0), REG(1)), 164 | 165 | INSN(READ, IMM(pdev->id), REG(1), REG(0)), 166 | INSN(EMIT, REG(0), 0, 0), 167 | 168 | INSN(ADD, REG(1), IMM(1), REG(1)), 169 | INSN(JNE, REG(1), IMM(16), IMM(-4)), 170 | }; 171 | struct mdio_prog prog = MDIO_PROG_FIXED(insns); 172 | int err; 173 | 174 | switch (FIELD_GET(MDIO_PHY_ID_DEVAD, pdev->id)) { 175 | case MDIO_MMD_PMAPMD: 176 | dev = &pma_print_device; 177 | break; 178 | case MDIO_MMD_PCS: 179 | dev = &pcs_print_device; 180 | break; 181 | case MDIO_MMD_AN: 182 | dev = &an_print_device; 183 | break; 184 | default: 185 | dev = &null_print_device; 186 | break; 187 | } 188 | 189 | err = mdio_xfer(pdev->dev.bus, &prog, mmd_status_cb, (void *)dev); 190 | if (err) { 191 | fprintf(stderr, "ERROR: Unable to read status (%d)\n", err); 192 | return 1; 193 | } 194 | 195 | return 0; 196 | } 197 | 198 | int mmd_exec_with(const struct mdio_driver *drv, 199 | const char *bus, int argc, char **argv) 200 | { 201 | struct phy_device pdev = { 202 | .dev = { 203 | .bus = bus, 204 | .driver = drv, 205 | 206 | .mem = { 207 | .stride = 1, 208 | .width = 16, 209 | .max = UINT16_MAX, 210 | }, 211 | }, 212 | }; 213 | char *arg; 214 | 215 | arg = argv_pop(&argc, &argv); 216 | if (!arg || mdio_parse_dev(arg, &pdev.id, true)) 217 | return 1; 218 | 219 | if (!(pdev.id & MDIO_PHY_ID_C45)) { 220 | fprintf(stderr, "ERROR: Expected Clause 45 (PRTAD:DEVAD) address\n"); 221 | return 1; 222 | } 223 | 224 | arg = argv_peek(argc, argv); 225 | if (!arg || !strcmp(arg, "status")) 226 | return mmd_exec_status(&pdev, argc, argv); 227 | 228 | return mdio_common_exec(&pdev.dev, argc, argv); 229 | } 230 | 231 | int mmd_exec(const char *bus, int argc, char **argv) 232 | { 233 | return mmd_exec_with(&phy_driver, bus, argc, argv); 234 | } 235 | DEFINE_CMD("mmd", mmd_exec); 236 | 237 | static int mmd_c22_read(struct mdio_device *dev, struct mdio_prog *prog, 238 | uint32_t reg) 239 | { 240 | struct phy_device *pdev = (void *)dev; 241 | uint8_t prtad = (pdev->id & MDIO_PHY_ID_PRTAD) >> 5; 242 | uint8_t devad = pdev->id & MDIO_PHY_ID_DEVAD; 243 | uint16_t ctrl = devad; 244 | 245 | /* Set the address */ 246 | ctrl = devad; 247 | mdio_prog_push(prog, INSN(WRITE, IMM(prtad), IMM(13), IMM(ctrl))); 248 | mdio_prog_push(prog, INSN(WRITE, IMM(prtad), IMM(14), IMM(reg))); 249 | 250 | /* Read out the data */ 251 | ctrl |= 1 << 14; 252 | mdio_prog_push(prog, INSN(WRITE, IMM(prtad), IMM(13), IMM(ctrl))); 253 | mdio_prog_push(prog, INSN(READ, IMM(prtad), IMM(14), REG(0))); 254 | return 0; 255 | } 256 | 257 | static int mmd_c22_write(struct mdio_device *dev, struct mdio_prog *prog, 258 | uint32_t reg, uint32_t val) 259 | { 260 | struct phy_device *pdev = (void *)dev; 261 | uint8_t prtad = (pdev->id & MDIO_PHY_ID_PRTAD) >> 5; 262 | uint8_t devad = pdev->id & MDIO_PHY_ID_DEVAD; 263 | uint16_t ctrl = devad; 264 | 265 | /* Set the address */ 266 | ctrl = devad; 267 | mdio_prog_push(prog, INSN(WRITE, IMM(prtad), IMM(13), IMM(ctrl))); 268 | mdio_prog_push(prog, INSN(WRITE, IMM(prtad), IMM(14), IMM(reg))); 269 | 270 | /* Write the data */ 271 | ctrl |= 1 << 14; 272 | mdio_prog_push(prog, INSN(WRITE, IMM(prtad), IMM(13), IMM(ctrl))); 273 | mdio_prog_push(prog, INSN(WRITE, IMM(prtad), IMM(14), val)); 274 | return 0; 275 | } 276 | 277 | static const struct mdio_driver mmd_c22_driver = { 278 | .read = mmd_c22_read, 279 | .write = mmd_c22_write, 280 | }; 281 | 282 | int mmd_c22_exec(const char *bus, int argc, char **argv) 283 | { 284 | return mmd_exec_with(&mmd_c22_driver, bus, argc, argv); 285 | } 286 | DEFINE_CMD("mmd-c22", mmd_c22_exec); 287 | -------------------------------------------------------------------------------- /src/mdio/print_phy.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include "linux/bitfield.h" 8 | #include "mdio.h" 9 | 10 | void print_bool(const char *name, int on) 11 | { 12 | if (on) 13 | fputs("\e[1m+", stdout); 14 | else 15 | fputs("-", stdout); 16 | 17 | fputs(name, stdout); 18 | 19 | if (on) 20 | fputs("\e[0m", stdout); 21 | } 22 | 23 | static const char *get_speed(uint16_t val) 24 | { 25 | switch (val & MDIO_CTRL1_SPEEDSELEXT) { 26 | case MDIO_PMA_CTRL1_SPEED1000: 27 | return "1000"; 28 | case MDIO_PMA_CTRL1_SPEED100: 29 | return "100"; 30 | case 0: 31 | return "10"; 32 | } 33 | 34 | switch (val & MDIO_CTRL1_SPEEDSEL) { 35 | case (MDIO_CTRL1_SPEEDSELEXT | 0x24): 36 | return "400g"; 37 | case (MDIO_CTRL1_SPEEDSELEXT | 0x20): 38 | return "200g"; 39 | case (MDIO_CTRL1_SPEEDSELEXT | 0x0c): 40 | return "100g"; 41 | case (MDIO_CTRL1_SPEEDSELEXT | 0x08): 42 | return "40g"; 43 | case (MDIO_CTRL1_SPEEDSELEXT | 0x10): 44 | return "25g"; 45 | case MDIO_CTRL1_SPEED10G: 46 | return "10g"; 47 | case MDIO_CTRL1_SPEED2_5G: 48 | return "2.5g"; 49 | case MDIO_CTRL1_SPEED5G: 50 | return "5g"; 51 | case MDIO_CTRL1_SPEED10P2B: 52 | return "10-ts/2-tl"; 53 | default: 54 | return "unknown"; 55 | } 56 | } 57 | 58 | void print_phy_bmcr(uint16_t val) 59 | { 60 | printf("BMCR(0x00): %#.4x\n", val); 61 | 62 | fputs(" flags: ", stdout); 63 | print_bool("reset", val & BMCR_RESET); 64 | putchar(' '); 65 | 66 | print_bool("loopback", val & BMCR_LOOPBACK); 67 | putchar(' '); 68 | 69 | print_bool("aneg-enable", val & BMCR_ANENABLE); 70 | putchar(' '); 71 | 72 | print_bool("power-down", val & BMCR_PDOWN); 73 | putchar(' '); 74 | 75 | print_bool("isolate", val & BMCR_ISOLATE); 76 | putchar(' '); 77 | 78 | print_bool("aneg-restart", val & BMCR_ANRESTART); 79 | fputs("\n" 80 | " ", stdout); 81 | 82 | print_bool("collision-test", val & BMCR_CTST); 83 | putchar('\n'); 84 | 85 | printf(" speed: %s-%s\n", get_speed(val), 86 | (val & BMCR_FULLDPLX) ? "full" : "half"); 87 | } 88 | 89 | void print_phy_bmsr(uint16_t val) 90 | { 91 | printf("BMSR(0x01): %#.4x\n", val); 92 | 93 | fputs(" capabilities: ", stdout); 94 | print_bool("100-t4", val & BMSR_100BASE4); 95 | putchar(' '); 96 | 97 | print_bool("100-tx-f", val & BMSR_100FULL); 98 | putchar(' '); 99 | 100 | print_bool("100-tx-h", val & BMSR_100HALF); 101 | putchar(' '); 102 | 103 | print_bool("10-t-f", val & BMSR_10FULL); 104 | putchar(' '); 105 | 106 | print_bool("10-t-h", val & BMSR_10HALF); 107 | putchar(' '); 108 | 109 | print_bool("100-t2-f", val & BMSR_100FULL2); 110 | putchar(' '); 111 | 112 | print_bool("100-t2-h", val & BMSR_100HALF2); 113 | putchar('\n'); 114 | 115 | fputs( " flags: ", stdout); 116 | print_bool("ext-status", val & BMSR_ESTATEN); 117 | putchar(' '); 118 | 119 | print_bool("aneg-complete", val & BMSR_ANEGCOMPLETE); 120 | putchar(' '); 121 | 122 | print_bool("remote-fault", val & BMSR_RFAULT); 123 | putchar(' '); 124 | 125 | print_bool("aneg-capable", val & BMSR_ANEGCAPABLE); 126 | putchar(' '); 127 | 128 | print_bool("link", val & BMSR_LSTATUS); 129 | fputs("\n" 130 | " ", stdout); 131 | 132 | print_bool("jabber", val & BMSR_JCD); 133 | putchar(' '); 134 | 135 | print_bool("ext-register", val & BMSR_ERCAP); 136 | putchar('\n'); 137 | } 138 | 139 | void print_phy_id(uint16_t id_hi, uint16_t id_lo) 140 | { 141 | uint32_t id = (id_hi << 16) | id_lo; 142 | 143 | printf("ID(0x02/0x03): %#.8x\n", id); 144 | } 145 | 146 | void print_phy_estatus(uint16_t val) 147 | { 148 | printf("ESTATUS(0x0F): %#.4x\n", val); 149 | 150 | fputs(" capabilities: ", stdout); 151 | print_bool("1000-x-f", val & ESTATUS_1000_XFULL); 152 | putchar(' '); 153 | 154 | print_bool("1000-x-h", val & ESTATUS_1000_XHALF); 155 | putchar(' '); 156 | 157 | print_bool("1000-t-f", val & ESTATUS_1000_TFULL); 158 | putchar(' '); 159 | 160 | print_bool("1000-t-h", val & ESTATUS_1000_THALF); 161 | putchar('\n'); 162 | } 163 | 164 | void print_mmd_devid(uint16_t id_hi, uint16_t id_lo) 165 | { 166 | uint32_t id = (id_hi << 16) | id_lo; 167 | 168 | printf("DEVID(0x02/0x03): %#.8x\n", id); 169 | } 170 | 171 | void print_mmd_pkgid(uint16_t id_hi, uint16_t id_lo) 172 | { 173 | uint32_t id = (id_hi << 16) | id_lo; 174 | 175 | printf("PKGID(0x0E/0x0F): %#.8x\n", id); 176 | } 177 | 178 | void print_mmd_devs(uint16_t devs_hi, uint16_t devs_lo) 179 | { 180 | uint32_t devs = devs_hi << 16 | devs_lo; 181 | 182 | printf("DEVS(0x06/0x05): %#.8x\n", devs); 183 | 184 | fputs(" devices: ", stdout); 185 | print_bool("vendor2", devs & MDIO_DEVS_VEND2); 186 | putchar(' '); 187 | 188 | print_bool("vendor1", devs & MDIO_DEVS_VEND1); 189 | putchar(' '); 190 | 191 | print_bool("c22-ext", devs & MDIO_DEVS_C22EXT); 192 | putchar(' '); 193 | 194 | print_bool("power-unit", devs & MDIO_DEVS_PRESENT(13)); 195 | putchar(' '); 196 | 197 | print_bool("ofdm", devs & MDIO_DEVS_PRESENT(12)); 198 | putchar(' '); 199 | 200 | print_bool("pma4", devs & MDIO_DEVS_PRESENT(11)); 201 | putchar(' '); 202 | 203 | print_bool("pma3", devs & MDIO_DEVS_PRESENT(10)); 204 | putchar(' '); 205 | 206 | print_bool("pma2", devs & MDIO_DEVS_PRESENT(9)); 207 | putchar(' '); 208 | 209 | print_bool("pma1", devs & MDIO_DEVS_PRESENT(8)); 210 | fputs("\n" 211 | " ", stdout); 212 | 213 | print_bool("aneg", devs & MDIO_DEVS_AN); 214 | putchar(' '); 215 | 216 | print_bool("tc", devs & MDIO_DEVS_TC); 217 | putchar(' '); 218 | 219 | print_bool("dte-xs", devs & MDIO_DEVS_DTEXS); 220 | putchar(' '); 221 | 222 | print_bool("phy-xs", devs & MDIO_DEVS_PHYXS); 223 | putchar(' '); 224 | 225 | print_bool("pcs", devs & MDIO_DEVS_PCS); 226 | putchar(' '); 227 | 228 | print_bool("wis", devs & MDIO_DEVS_WIS); 229 | putchar(' '); 230 | 231 | print_bool("pma/pmd", devs & MDIO_DEVS_PMAPMD); 232 | putchar(' '); 233 | 234 | print_bool("c22", devs & MDIO_DEVS_C22PRESENT); 235 | putchar('\n'); 236 | } 237 | 238 | static void print_pma_ctrl1(uint16_t val) 239 | { 240 | printf("CTRL1(0x00): %#.4x\n", val); 241 | 242 | fputs(" flags: ", stdout); 243 | print_bool("reset", val & MDIO_CTRL1_RESET); 244 | putchar(' '); 245 | 246 | print_bool("low-power", val & MDIO_CTRL1_LPOWER); 247 | putchar(' '); 248 | 249 | print_bool("remote-loopback", val & BIT(1)); 250 | putchar(' '); 251 | 252 | print_bool("local-loopback", val & MDIO_PMA_CTRL1_LOOPBACK); 253 | putchar('\n'); 254 | 255 | printf(" speed: %s\n", get_speed(val)); 256 | } 257 | 258 | static void print_pma_stat1(uint16_t val) 259 | { 260 | printf("STAT1(0x01): %#.4x\n", val); 261 | 262 | fputs(" capabilities: ", stdout); 263 | print_bool("pias", val & BIT(9)); 264 | putchar(' '); 265 | 266 | print_bool("peas", val & BIT(8)); 267 | putchar(' '); 268 | 269 | print_bool("low-power", val & MDIO_STAT1_LPOWERABLE); 270 | putchar('\n'); 271 | 272 | fputs(" flags: ", stdout); 273 | print_bool("fault", val & MDIO_STAT1_FAULT); 274 | putchar(' '); 275 | 276 | print_bool("link", val & MDIO_STAT1_LSTATUS); 277 | putchar('\n'); 278 | } 279 | 280 | static void print_pma_speed(uint16_t val) 281 | { 282 | printf("SPEED(0x04): %#.4x\n", val); 283 | 284 | fputs(" capabilities: ", stdout); 285 | print_bool("400g", val & BIT(15)); 286 | putchar(' '); 287 | 288 | print_bool("5g", val & BIT(14)); 289 | putchar(' '); 290 | 291 | print_bool("2.5g", val & BIT(13)); 292 | putchar(' '); 293 | 294 | print_bool("200g", val & BIT(12)); 295 | putchar(' '); 296 | 297 | print_bool("25g", val & BIT(11)); 298 | putchar(' '); 299 | 300 | print_bool("10g-xr", val & BIT(10)); 301 | putchar(' '); 302 | 303 | print_bool("100g", val & BIT(9)); 304 | putchar(' '); 305 | 306 | print_bool("40g", val & BIT(8)); 307 | putchar(' '); 308 | 309 | print_bool("10g/1g", val & BIT(7)); 310 | putchar(' '); 311 | 312 | print_bool("10", val & MDIO_PMA_SPEED_10); 313 | putchar(' '); 314 | 315 | print_bool("100", val & MDIO_PMA_SPEED_100); 316 | fputs("\n" 317 | " ", stdout); 318 | 319 | print_bool("1000", val & MDIO_PMA_SPEED_1000); 320 | putchar(' '); 321 | 322 | print_bool("10-ts", val & MDIO_PMA_SPEED_10P); 323 | putchar(' '); 324 | 325 | print_bool("2-tl", val & MDIO_PMA_SPEED_2B); 326 | putchar(' '); 327 | 328 | print_bool("10g", val & MDIO_SPEED_10G); 329 | putchar('\n'); 330 | } 331 | 332 | static void print_pma_ctrl2(uint16_t val) 333 | { 334 | const char *type; 335 | static const char *const pma_type[0x80] = { 336 | [0x5c] = "400g-lr8", 337 | [0x5b] = "400g-fr8", 338 | [0x5a] = "400g-dr4", 339 | [0x59] = "400g-sr16", 340 | [0x55] = "200g-lr4", 341 | [0x54] = "200g-fr4", 342 | [0x53] = "200g-dr4", 343 | [0x3d] = "-t1", 344 | [0x3a] = "25g-sr", 345 | [0x39] = "25g-kr", 346 | [0x38] = "25g-cr", 347 | [0x37] = "25g-t", 348 | [0x36] = "25g-er", 349 | [0x35] = "25g-lr", 350 | [0x34] = "-h", 351 | [0x33] = "10g-xr-u", 352 | [0x32] = "10g-xr-d", 353 | [MDIO_PMA_CTRL2_5GBT] = "5g-t", 354 | [MDIO_PMA_CTRL2_2_5GBT] = "2.5g-t", 355 | [0x2f] = "100g-sr4", 356 | [0x2e] = "100g-cr4", 357 | [0x2d] = "100g-kr4", 358 | [0x2c] = "100g-kp4", 359 | [0x2b] = "100g-er4", 360 | [0x2a] = "100g-lr4", 361 | [0x29] = "100g-sr10", 362 | [0x28] = "100g-cr10", 363 | [0x26] = "40g-t", 364 | [0x25] = "40g-er4", 365 | [0x24] = "40g-fr", 366 | [0x23] = "40g-lr4", 367 | [0x22] = "40g-sr4", 368 | [0x21] = "40g-cr4", 369 | [0x20] = "40g-kr4", 370 | [0x1f] = "10/1g-prx-u4", 371 | [0x1e] = "10g-pr-u4", 372 | [0x1d] = "10/1g-prx-d4", 373 | [0x1c] = "10g-pr-d4", 374 | [0x1a] = "10g-pr-u3", 375 | [0x19] = "10g-pr-u1", 376 | [0x18] = "10/1g-prx-u3", 377 | [0x17] = "10/1g-prx-u2", 378 | [0x16] = "10/1g-prx-u1", 379 | [0x15] = "10g-pr-d3", 380 | [0x14] = "10g-pr-d2", 381 | [0x13] = "10g-pr-d1", 382 | [0x12] = "10/1g-prx-d3", 383 | [0x11] = "10/1g-prx-d2", 384 | [0x10] = "10/1g-prx-d1", 385 | [MDIO_PMA_CTRL2_10BT] = "10-t", 386 | [MDIO_PMA_CTRL2_100BTX] = "100-tx", 387 | [MDIO_PMA_CTRL2_1000BKX] = "1000-kx", 388 | [MDIO_PMA_CTRL2_1000BT] = "1000-t", 389 | [MDIO_PMA_CTRL2_10GBKR] = "10g-kr", 390 | [MDIO_PMA_CTRL2_10GBKX4] = "10g-kx4", 391 | [MDIO_PMA_CTRL2_10GBT] = "10g-t", 392 | [MDIO_PMA_CTRL2_10GBLRM] = "10g-lrm", 393 | [MDIO_PMA_CTRL2_10GBSR] = "10g-sr", 394 | [MDIO_PMA_CTRL2_10GBLR] = "10g-lr", 395 | [MDIO_PMA_CTRL2_10GBER] = "10g-er", 396 | [MDIO_PMA_CTRL2_10GBLX4] = "10g-lx4", 397 | [MDIO_PMA_CTRL2_10GBSW] = "10g-sw", 398 | [MDIO_PMA_CTRL2_10GBLW] = "10g-lw", 399 | [MDIO_PMA_CTRL2_10GBEW] = "10g-ew", 400 | [MDIO_PMA_CTRL2_10GBCX4] = "10g-cx4", 401 | }; 402 | 403 | printf("CTRL2(0x07): %#.4x\n", val); 404 | 405 | fputs(" flags: ", stdout); 406 | print_bool("pias", val & BIT(9)); 407 | putchar(' '); 408 | 409 | print_bool("peas", val & BIT(8)); 410 | putchar('\n'); 411 | 412 | type = pma_type[FIELD_GET(MDIO_PMA_CTRL2_TYPE, val)]; 413 | printf(" type: %s\n", type ?: "unknown"); 414 | } 415 | 416 | static void print_mmd_stat2_flags(uint16_t val) 417 | { 418 | fputs(" flags: ", stdout); 419 | print_bool("present", (val & MDIO_STAT2_DEVPRST) == 420 | MDIO_STAT2_DEVPRST_VAL); 421 | putchar(' '); 422 | 423 | print_bool("tx-fault", val & MDIO_STAT2_TXFAULT); 424 | putchar(' '); 425 | 426 | print_bool("rx-fault", val & MDIO_STAT2_RXFAULT); 427 | putchar('\n'); 428 | } 429 | 430 | static void print_pma_stat2(uint16_t val) 431 | { 432 | printf("STAT2(0x08): %#.4x\n", val); 433 | 434 | fputs(" capabilities: ", stdout); 435 | print_bool("tx-fault", val & MDIO_PMA_STAT2_TXFLTABLE); 436 | putchar(' '); 437 | 438 | print_bool("rx-fault", val & MDIO_PMA_STAT2_RXFLTABLE); 439 | putchar(' '); 440 | 441 | print_bool("ext-register", val & MDIO_PMA_STAT2_EXTABLE); 442 | putchar(' '); 443 | 444 | print_bool("tx-disable", val & MDIO_PMD_STAT2_TXDISAB); 445 | putchar(' '); 446 | 447 | print_bool("local-loopback", val & MDIO_PMA_STAT2_LBABLE); 448 | fputs("\n" 449 | " ", stdout); 450 | 451 | print_bool("10g-sr", val & MDIO_PMA_STAT2_10GBSR); 452 | putchar(' '); 453 | 454 | print_bool("10g-lr", val & MDIO_PMA_STAT2_10GBLR); 455 | putchar(' '); 456 | 457 | print_bool("10g-er", val & MDIO_PMA_STAT2_10GBER); 458 | putchar(' '); 459 | 460 | print_bool("10g-lx4", val & MDIO_PMA_STAT2_10GBLX4); 461 | putchar(' '); 462 | 463 | print_bool("10g-sw", val & MDIO_PMA_STAT2_10GBSW); 464 | putchar(' '); 465 | 466 | print_bool("10g-lw", val & MDIO_PMA_STAT2_10GBLW); 467 | putchar(' '); 468 | 469 | print_bool("10g-ew", val & MDIO_PMA_STAT2_10GBEW); 470 | putchar('\n'); 471 | 472 | print_mmd_stat2_flags(val); 473 | } 474 | 475 | static void print_pma_extable(uint16_t val) 476 | { 477 | printf("EXTABLE(0x0B): %#.4x\n", val); 478 | 479 | fputs(" capabilities: ", stdout); 480 | print_bool("10g-cx4", val & MDIO_PMA_EXTABLE_10GCX4); 481 | putchar(' '); 482 | 483 | print_bool("10g-lrm", val & MDIO_PMA_EXTABLE_10GBLRM); 484 | putchar(' '); 485 | 486 | print_bool("10g-t", val & MDIO_PMA_EXTABLE_10GBT); 487 | putchar(' '); 488 | 489 | print_bool("10g-kx4", val & MDIO_PMA_EXTABLE_10GBKX4); 490 | putchar(' '); 491 | 492 | print_bool("10g-kr", val & MDIO_PMA_EXTABLE_10GBKR); 493 | putchar(' '); 494 | 495 | print_bool("1000-t", val & MDIO_PMA_EXTABLE_1000BT); 496 | putchar(' '); 497 | 498 | print_bool("1000-kx", val & MDIO_PMA_EXTABLE_1000BKX); 499 | fputs("\n" 500 | " ", stdout); 501 | 502 | print_bool("100-tx", val & MDIO_PMA_EXTABLE_100BTX); 503 | putchar(' '); 504 | 505 | print_bool("10-t", val & MDIO_PMA_EXTABLE_10BT); 506 | putchar(' '); 507 | 508 | print_bool("p2mp", val & BIT(9)); 509 | putchar(' '); 510 | 511 | print_bool("40g/100g", val & BIT(10)); 512 | putchar(' '); 513 | 514 | print_bool("1000/100-t1", val & BIT(11)); 515 | putchar(' '); 516 | 517 | print_bool("25g", val & BIT(12)); 518 | putchar(' '); 519 | 520 | print_bool("200g/400g", val & BIT(13)); 521 | fputs("\n" 522 | " ", stdout); 523 | 524 | print_bool("2.5g/5g", val & MDIO_PMA_EXTABLE_NBT); 525 | putchar(' '); 526 | 527 | print_bool("1000-h", val & BIT(15)); 528 | putchar('\n'); 529 | } 530 | 531 | static void print_pma_extra(uint32_t *data) 532 | { 533 | print_pma_ctrl2(data[7]); 534 | putchar('\n'); 535 | print_pma_stat2(data[8]); 536 | 537 | if (data[8] & MDIO_PMA_STAT2_EXTABLE) { 538 | putchar('\n'); 539 | print_pma_extable(data[11]); 540 | } 541 | } 542 | 543 | const struct mmd_print_device pma_print_device = { 544 | .print_ctrl1 = print_pma_ctrl1, 545 | .print_stat1 = print_pma_stat1, 546 | .print_speed = print_pma_speed, 547 | .print_extra = print_pma_extra, 548 | }; 549 | 550 | static void print_pcs_ctrl1(uint16_t val) 551 | { 552 | printf("CTRL1(0x00): %#.4x\n", val); 553 | 554 | fputs(" flags: ", stdout); 555 | print_bool("reset", val & MDIO_CTRL1_RESET); 556 | putchar(' '); 557 | 558 | print_bool("loopback", val & MDIO_PCS_CTRL1_LOOPBACK); 559 | putchar(' '); 560 | 561 | print_bool("low-power", val & MDIO_CTRL1_LPOWER); 562 | putchar(' '); 563 | 564 | print_bool("lpi-clock-stop", val & MDIO_PCS_CTRL1_CLKSTOP_EN); 565 | putchar(' '); 566 | 567 | putchar('\n'); 568 | 569 | printf(" speed: %s\n", get_speed(val)); 570 | } 571 | 572 | static void print_pcs_stat1(uint16_t val) 573 | { 574 | printf("STAT1(0x01): %#.4x\n", val); 575 | 576 | fputs(" capabilities: ", stdout); 577 | print_bool("lpi-clock-stop", val & BIT(6)); 578 | putchar(' '); 579 | 580 | print_bool("low-power", val & MDIO_STAT1_LPOWERABLE); 581 | putchar('\n'); 582 | 583 | fputs(" flags: ", stdout); 584 | print_bool("rx-lpi-recv", val & BIT(11)); 585 | putchar(' '); 586 | 587 | print_bool("tx-lpi-recv", val & BIT(10)); 588 | putchar(' '); 589 | 590 | print_bool("rx-lpi-ind", val & BIT(9)); 591 | putchar(' '); 592 | 593 | print_bool("tx-lpi-ind", val & BIT(8)); 594 | putchar(' '); 595 | 596 | print_bool("fault", val & MDIO_STAT1_FAULT); 597 | putchar(' '); 598 | 599 | print_bool("link", val & MDIO_STAT1_LSTATUS); 600 | putchar('\n'); 601 | } 602 | 603 | static void print_pcs_speed(uint16_t val) 604 | { 605 | printf("SPEED(0x04): %#.4x\n", val); 606 | 607 | fputs(" capabilities: ", stdout); 608 | print_bool("400g", val & BIT(9)); 609 | putchar(' '); 610 | 611 | print_bool("200g", val & BIT(8)); 612 | putchar(' '); 613 | 614 | print_bool("5g", val & BIT(7)); 615 | putchar(' '); 616 | 617 | print_bool("2.5g", val & BIT(5)); 618 | putchar(' '); 619 | 620 | print_bool("25g", val & BIT(4)); 621 | putchar(' '); 622 | 623 | print_bool("100g", val & BIT(3)); 624 | putchar(' '); 625 | 626 | print_bool("40g", val & BIT(2)); 627 | putchar(' '); 628 | 629 | print_bool("10-ts/2-tl", val & MDIO_PCS_SPEED_10P2B); 630 | putchar(' '); 631 | 632 | print_bool("10g", val & MDIO_SPEED_10G); 633 | putchar('\n'); 634 | } 635 | 636 | static void print_pcs_ctrl2(uint16_t val) 637 | { 638 | const char *type; 639 | static const char *const pcs_type[0x10] = { 640 | [0xd] = "400g-r", 641 | [0xc] = "200g-r", 642 | [0xb] = "5g-t", 643 | [0xa] = "2.5g-t", 644 | [0x9] = "25g-t", 645 | [0x7] = "25g-r", 646 | [0x6] = "40g-t", 647 | [0x5] = "100g-r", 648 | [0x4] = "40g-r", 649 | [MDIO_PCS_CTRL2_10GBT] = "10g-t", 650 | [MDIO_PCS_CTRL2_10GBW] = "10g-w", 651 | [MDIO_PCS_CTRL2_10GBX] = "10g-x", 652 | [MDIO_PCS_CTRL2_10GBR] = "10g-r", 653 | }; 654 | 655 | printf("CTRL2(0x07): %#.4x\n", val); 656 | 657 | type = pcs_type[FIELD_GET(MDIO_PCS_CTRL2_TYPE, val)]; 658 | printf(" type: %s\n", type ?: "unknown"); 659 | } 660 | 661 | static void print_pcs_stat2(uint16_t val) 662 | { 663 | printf("STAT2(0x08): %#.4x\n", val); 664 | 665 | fputs(" capabilities: ", stdout); 666 | print_bool("5g-t", val & BIT(13)); 667 | putchar(' '); 668 | 669 | print_bool("2.5g-t", val & BIT(12)); 670 | putchar(' '); 671 | 672 | print_bool("25g-t", val & BIT(9)); 673 | putchar(' '); 674 | 675 | print_bool("25g-r", val & BIT(7)); 676 | putchar(' '); 677 | 678 | print_bool("40g-t", val & BIT(6)); 679 | putchar(' '); 680 | 681 | print_bool("100g-r", val & BIT(5)); 682 | putchar(' '); 683 | 684 | print_bool("40g-r", val & BIT(4)); 685 | putchar(' '); 686 | 687 | print_bool("10g-t", val & BIT(3)); 688 | putchar(' '); 689 | 690 | print_bool("10g-w", val & MDIO_PCS_STAT2_10GBW); 691 | fputs("\n" 692 | " ", stdout); 693 | 694 | print_bool("10g-x", val & MDIO_PCS_STAT2_10GBX); 695 | putchar(' '); 696 | 697 | print_bool("10g-r", val & MDIO_PCS_STAT2_10GBR); 698 | putchar('\n'); 699 | 700 | print_mmd_stat2_flags(val); 701 | } 702 | 703 | static void print_pcs_extra(uint32_t *data) 704 | { 705 | print_pcs_ctrl2(data[7]); 706 | putchar('\n'); 707 | print_pcs_stat2(data[8]); 708 | } 709 | 710 | const struct mmd_print_device pcs_print_device = { 711 | .print_ctrl1 = print_pcs_ctrl1, 712 | .print_stat1 = print_pcs_stat1, 713 | .print_speed = print_pcs_speed, 714 | .print_extra = print_pcs_extra, 715 | }; 716 | 717 | static void print_an_ctrl1(uint16_t val) 718 | { 719 | printf("CTRL1(0x00): %#.4x\n", val); 720 | 721 | fputs(" flags: ", stdout); 722 | print_bool("reset", val & MDIO_CTRL1_RESET); 723 | putchar(' '); 724 | 725 | print_bool("ext-page", val & MDIO_AN_CTRL1_XNP); 726 | putchar(' '); 727 | 728 | print_bool("aneg-enable", val & MDIO_AN_CTRL1_ENABLE); 729 | putchar(' '); 730 | 731 | print_bool("aneg-restart", val & MDIO_AN_CTRL1_RESTART); 732 | putchar('\n'); 733 | } 734 | 735 | static void print_an_stat1(uint16_t val) 736 | { 737 | printf("STAT1(0x01): %#.4x\n", val); 738 | 739 | fputs(" capabilities: ", stdout); 740 | print_bool("aneg-capable", val & MDIO_AN_STAT1_ABLE); 741 | putchar(' '); 742 | 743 | print_bool("partner-capable", val & MDIO_AN_STAT1_LPABLE); 744 | putchar('\n'); 745 | 746 | fputs(" flags: ", stdout); 747 | print_bool("ext-page", val & MDIO_AN_STAT1_XNP); 748 | putchar(' '); 749 | 750 | print_bool("parallel-fault", val & MDIO_STAT1_FAULT); 751 | putchar(' '); 752 | 753 | print_bool("page", val & MDIO_AN_STAT1_PAGE); 754 | putchar(' '); 755 | 756 | print_bool("aneg-complete", val & MDIO_AN_STAT1_COMPLETE); 757 | putchar(' '); 758 | 759 | print_bool("remote-fault", val & MDIO_AN_STAT1_RFAULT); 760 | fputs("\n" 761 | " ", stdout); 762 | 763 | print_bool("link", val & MDIO_STAT1_LSTATUS); 764 | putchar('\n'); 765 | } 766 | 767 | const struct mmd_print_device an_print_device = { 768 | .print_ctrl1 = print_an_ctrl1, 769 | .print_stat1 = print_an_stat1, 770 | }; 771 | -------------------------------------------------------------------------------- /src/mdio/xrs.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "mdio.h" 7 | 8 | struct xrs_device { 9 | struct mdio_device dev; 10 | uint16_t id; 11 | }; 12 | 13 | #define XRS_IBA0 0x10 14 | #define XRS_IBA1 0x11 15 | #define XRS_IBD 0x14 16 | 17 | int xrs_read(struct mdio_device *dev, struct mdio_prog *prog, uint32_t reg) 18 | { 19 | struct xrs_device *xdev = (void *)dev; 20 | uint16_t iba[2] = { reg & 0xfffe, reg >> 16 }; 21 | 22 | mdio_prog_push(prog, INSN(WRITE, IMM(xdev->id), IMM(XRS_IBA1), IMM(iba[1]))); 23 | mdio_prog_push(prog, INSN(WRITE, IMM(xdev->id), IMM(XRS_IBA0), IMM(iba[0]))); 24 | mdio_prog_push(prog, INSN(READ, IMM(xdev->id), IMM(XRS_IBD), REG(0))); 25 | return 0; 26 | } 27 | 28 | int xrs_write(struct mdio_device *dev, struct mdio_prog *prog, 29 | uint32_t reg, uint32_t val) 30 | { 31 | struct xrs_device *xdev = (void *)dev; 32 | uint16_t iba[2] = { (reg & 0xfffe) | 1, reg >> 16 }; 33 | 34 | mdio_prog_push(prog, INSN(WRITE, IMM(xdev->id), IMM(XRS_IBD), IMM(val))); 35 | mdio_prog_push(prog, INSN(WRITE, IMM(xdev->id), IMM(XRS_IBA1), IMM(iba[1]))); 36 | mdio_prog_push(prog, INSN(WRITE, IMM(xdev->id), IMM(XRS_IBA0), IMM(iba[0]))); 37 | return 0; 38 | } 39 | 40 | static const struct mdio_driver xrs_driver = { 41 | .read = xrs_read, 42 | .write = xrs_write, 43 | }; 44 | 45 | int xrs_exec(const char *bus, int argc, char **argv) 46 | { 47 | struct xrs_device xdev = { 48 | .dev = { 49 | .bus = bus, 50 | .driver = &xrs_driver, 51 | 52 | .mem = { 53 | .max = UINT32_MAX, 54 | .stride = 2, 55 | .width = 16, 56 | }, 57 | }, 58 | }; 59 | char *arg; 60 | 61 | arg = argv_pop(&argc, &argv); 62 | if (!arg || mdio_parse_dev(arg, &xdev.id, true)) 63 | return 1; 64 | 65 | arg = argv_peek(argc, argv); 66 | if (!arg) 67 | return 1; 68 | 69 | return mdio_common_exec(&xdev.dev, argc, argv); 70 | } 71 | DEFINE_CMD("xrs", xrs_exec); 72 | -------------------------------------------------------------------------------- /src/mvls/.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | .deps/ 3 | mvls 4 | -------------------------------------------------------------------------------- /src/mvls/Makefile.am: -------------------------------------------------------------------------------- 1 | sbin_PROGRAMS = mvls 2 | 3 | mvls_SOURCES = mvls.c mvls.h devlink.c devlink.h mvls-show.c mvls-json.c queue.h 4 | mvls_CFLAGS = -Wall -Wextra -Werror -Wno-unused-parameter -I $(top_srcdir)/include $(mnl_CFLAGS) 5 | mvls_LDADD = $(mnl_LIBS) 6 | -------------------------------------------------------------------------------- /src/mvls/devlink.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "devlink.h" 7 | 8 | int devlink_attr_cb(const struct nlattr *attr, void *data) 9 | { 10 | const struct nlattr **tb = data; 11 | int type; 12 | 13 | /* Strategy: Hope for the best (tm) */ 14 | type = mnl_attr_get_type(attr); 15 | tb[type] = attr; 16 | return MNL_CB_OK; 17 | } 18 | 19 | int devlink_parse(const struct nlmsghdr *nlh, struct nlattr **tb) 20 | { 21 | struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh); 22 | 23 | mnl_attr_parse(nlh, sizeof(*genl), devlink_attr_cb, tb); 24 | return 0; 25 | } 26 | 27 | static struct nlmsghdr *__msg_prepare(struct devlink *dl, uint8_t cmd, 28 | uint16_t flags, uint32_t family, 29 | uint8_t version) 30 | { 31 | struct nlmsghdr *nlh; 32 | struct genlmsghdr *genl; 33 | 34 | nlh = mnl_nlmsg_put_header(dl->buf); 35 | nlh->nlmsg_type = family; 36 | nlh->nlmsg_flags = flags; 37 | dl->seq = time(NULL); 38 | nlh->nlmsg_seq = dl->seq; 39 | 40 | genl = mnl_nlmsg_put_extra_header(nlh, sizeof(struct genlmsghdr)); 41 | genl->cmd = cmd; 42 | genl->version = version; 43 | return nlh; 44 | } 45 | 46 | static int __cb_noop(const struct nlmsghdr *nlh, void *data) 47 | { 48 | return MNL_CB_OK; 49 | } 50 | 51 | static int __cb_error(const struct nlmsghdr *nlh, void *data) 52 | { 53 | const struct nlmsgerr *err = mnl_nlmsg_get_payload(nlh); 54 | 55 | /* Netlink subsystems returns the errno value with different signess */ 56 | if (err->error < 0) 57 | errno = -err->error; 58 | else 59 | errno = err->error; 60 | 61 | return err->error == 0 ? MNL_CB_STOP : MNL_CB_ERROR; 62 | } 63 | 64 | static int __cb_stop(const struct nlmsghdr *nlh, void *data) 65 | { 66 | int len = *(int *)NLMSG_DATA(nlh); 67 | 68 | if (len < 0) { 69 | errno = -len; 70 | return MNL_CB_ERROR; 71 | } 72 | return MNL_CB_STOP; 73 | } 74 | 75 | static mnl_cb_t __cb_array[NLMSG_MIN_TYPE] = { 76 | [NLMSG_NOOP] = __cb_noop, 77 | [NLMSG_ERROR] = __cb_error, 78 | [NLMSG_DONE] = __cb_stop, 79 | [NLMSG_OVERRUN] = __cb_noop, 80 | }; 81 | 82 | int __recv_run(struct devlink *dl, mnl_cb_t cb, void *data) 83 | { 84 | int err; 85 | 86 | do { 87 | err = mnl_socket_recvfrom(dl->nl, dl->buf, 88 | MNL_SOCKET_BUFFER_SIZE); 89 | if (err <= 0) 90 | break; 91 | 92 | err = mnl_cb_run2(dl->buf, err, dl->seq, dl->portid, 93 | cb, data, __cb_array, 4); 94 | } while (err > 0); 95 | 96 | return err; 97 | } 98 | 99 | struct nlmsghdr *devlink_msg_prepare(struct devlink *dl, uint8_t cmd, 100 | uint16_t flags) 101 | { 102 | return __msg_prepare(dl, cmd, flags, dl->family, DEVLINK_GENL_VERSION); 103 | } 104 | 105 | int devlink_query(struct devlink *dl, struct nlmsghdr *nlh, 106 | mnl_cb_t cb, void *data) 107 | { 108 | int err; 109 | 110 | err = mnl_socket_sendto(dl->nl, nlh, nlh->nlmsg_len); 111 | if (err < 0) 112 | return err; 113 | 114 | return __recv_run(dl, cb, data); 115 | } 116 | 117 | /* static int __region_new_cb(const struct nlattr *attr, void *data) */ 118 | /* { */ 119 | 120 | /* } */ 121 | 122 | int devlink_region_dup_cb(const struct nlmsghdr *nlh, void *_region) 123 | { 124 | struct nlattr *tbc[DEVLINK_ATTR_MAX + 1] = {}; 125 | struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {}; 126 | struct devlink_region *region = _region; 127 | const uint8_t *chunk; 128 | struct nlattr *attr; 129 | size_t offs, len; 130 | uint8_t *buf; 131 | int err; 132 | 133 | devlink_parse(nlh, tb); 134 | if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME] || 135 | !tb[DEVLINK_ATTR_REGION_CHUNKS]) 136 | return MNL_CB_ERROR; 137 | 138 | mnl_attr_for_each_nested(attr, tb[DEVLINK_ATTR_REGION_CHUNKS]) { 139 | err = mnl_attr_parse_nested(attr, devlink_attr_cb, tbc); 140 | if (err != MNL_CB_OK) 141 | return MNL_CB_ERROR; 142 | 143 | attr = tbc[DEVLINK_ATTR_REGION_CHUNK_DATA]; 144 | if (!attr) 145 | continue; 146 | 147 | chunk = mnl_attr_get_payload(attr); 148 | len = mnl_attr_get_payload_len(attr); 149 | 150 | attr = tbc[DEVLINK_ATTR_REGION_CHUNK_ADDR]; 151 | if (!attr) 152 | continue; 153 | 154 | offs = mnl_attr_get_u64(attr); 155 | 156 | if (offs + len > region->size) { 157 | region->size = offs + len; 158 | buf = realloc(region->data.u8, region->size); 159 | if (!buf) { 160 | if (region->data.u8) 161 | free(region->data.u8); 162 | 163 | memset(region, 0, sizeof(*region)); 164 | return MNL_CB_ERROR; 165 | } 166 | 167 | region->data.u8 = buf; 168 | } 169 | 170 | memcpy(region->data.u8 + offs, chunk, len); 171 | } 172 | 173 | return MNL_CB_OK; 174 | } 175 | 176 | static struct nlmsghdr * 177 | __region_msg_prepare(struct devlink *dl, struct devlink_addr *addr, int port, 178 | const char *name, uint8_t cmd, uint16_t flags) 179 | { 180 | struct nlmsghdr *nlh; 181 | 182 | nlh = devlink_msg_prepare(dl, cmd, flags); 183 | 184 | mnl_attr_put_strz(nlh, DEVLINK_ATTR_BUS_NAME, addr->bus); 185 | mnl_attr_put_strz(nlh, DEVLINK_ATTR_DEV_NAME, addr->dev); 186 | mnl_attr_put_strz(nlh, DEVLINK_ATTR_REGION_NAME, name); 187 | 188 | if (port >= 0) 189 | mnl_attr_put_u32(nlh, DEVLINK_ATTR_PORT_INDEX, port); 190 | 191 | mnl_attr_put_u32(nlh, DEVLINK_ATTR_REGION_SNAPSHOT_ID, 0xffffffff); 192 | return nlh; 193 | } 194 | 195 | int devlink_port_region_get(struct devlink *dl, struct devlink_addr *addr, 196 | int port, const char *name, 197 | mnl_cb_t cb, void *data) 198 | { 199 | struct nlmsghdr *nlh; 200 | int err; 201 | 202 | nlh = __region_msg_prepare(dl, addr, port, name, DEVLINK_CMD_REGION_NEW, 203 | NLM_F_REQUEST | NLM_F_ACK); 204 | 205 | err = devlink_query(dl, nlh, NULL, NULL); 206 | if (err) 207 | return err; 208 | 209 | nlh = __region_msg_prepare(dl, addr, port, name, DEVLINK_CMD_REGION_READ, 210 | NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP); 211 | err = devlink_query(dl, nlh, cb, data); 212 | 213 | nlh = __region_msg_prepare(dl, addr, port, name, DEVLINK_CMD_REGION_DEL, 214 | NLM_F_REQUEST | NLM_F_ACK); 215 | devlink_query(dl, nlh, cb, data); 216 | return err; 217 | } 218 | 219 | int devlink_region_get(struct devlink *dl, struct devlink_addr *addr, 220 | const char *name, mnl_cb_t cb, void *data) 221 | { 222 | return devlink_port_region_get(dl, addr, -1, name, cb, data); 223 | } 224 | 225 | static int get_family_id_attr_cb(const struct nlattr *attr, void *data) 226 | { 227 | const struct nlattr **tb = data; 228 | int type = mnl_attr_get_type(attr); 229 | 230 | if (mnl_attr_type_valid(attr, CTRL_ATTR_MAX) < 0) 231 | return MNL_CB_ERROR; 232 | 233 | if (type == CTRL_ATTR_FAMILY_ID && 234 | mnl_attr_validate(attr, MNL_TYPE_U16) < 0) 235 | return MNL_CB_ERROR; 236 | tb[type] = attr; 237 | return MNL_CB_OK; 238 | } 239 | 240 | static int get_family_id_cb(const struct nlmsghdr *nlh, void *data) 241 | { 242 | uint32_t *p_id = data; 243 | struct nlattr *tb[CTRL_ATTR_MAX + 1] = {}; 244 | struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh); 245 | 246 | mnl_attr_parse(nlh, sizeof(*genl), get_family_id_attr_cb, tb); 247 | if (!tb[CTRL_ATTR_FAMILY_ID]) 248 | return MNL_CB_ERROR; 249 | *p_id = mnl_attr_get_u16(tb[CTRL_ATTR_FAMILY_ID]); 250 | return MNL_CB_OK; 251 | } 252 | 253 | int devlink_open(struct devlink *dl) 254 | { 255 | struct nlmsghdr *nlh; 256 | int err; 257 | 258 | err = ENOMEM; 259 | dl->buf = malloc(MNL_SOCKET_BUFFER_SIZE); 260 | if (!dl->buf) 261 | goto err; 262 | 263 | err = EIO; 264 | dl->nl = mnl_socket_open(NETLINK_GENERIC); 265 | if (!dl->nl) 266 | goto err_free; 267 | 268 | err = mnl_socket_bind(dl->nl, 0, MNL_SOCKET_AUTOPID); 269 | if (err < 0) 270 | goto err_close; 271 | 272 | dl->portid = mnl_socket_get_portid(dl->nl); 273 | 274 | nlh = __msg_prepare(dl, CTRL_CMD_GETFAMILY, 275 | NLM_F_REQUEST | NLM_F_ACK, GENL_ID_CTRL, 1); 276 | mnl_attr_put_strz(nlh, CTRL_ATTR_FAMILY_NAME, DEVLINK_GENL_NAME); 277 | 278 | err = mnl_socket_sendto(dl->nl, nlh, nlh->nlmsg_len); 279 | if (err < 0) 280 | goto err_close; 281 | 282 | err = __recv_run(dl, get_family_id_cb, &dl->family); 283 | if (err < 0) 284 | goto err_close; 285 | 286 | return 0; 287 | 288 | err_close: 289 | mnl_socket_close(dl->nl); 290 | err_free: 291 | free(dl->buf); 292 | err: 293 | return err; 294 | } 295 | -------------------------------------------------------------------------------- /src/mvls/devlink.h: -------------------------------------------------------------------------------- 1 | #ifndef __DEVLINK_H 2 | #define __DEVLINK_H 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | struct mv88e6xxx_devlink_atu_entry { 11 | /* The FID is scattered over multiple registers. */ 12 | uint16_t fid; 13 | uint16_t atu_op; 14 | uint16_t atu_data; 15 | uint16_t atu_01; 16 | uint16_t atu_23; 17 | uint16_t atu_45; 18 | }; 19 | 20 | struct mv88e6xxx_devlink_vtu_entry { 21 | uint16_t fid; 22 | uint16_t sid; 23 | uint16_t op; 24 | uint16_t vid; 25 | uint16_t data[3]; 26 | uint16_t resvd; 27 | }; 28 | 29 | struct mv88e6xxx_devlink_stu_entry { 30 | uint16_t sid; 31 | uint16_t vid; 32 | uint16_t data[3]; 33 | uint16_t resvd; 34 | }; 35 | 36 | struct devlink_addr { 37 | char *bus; 38 | char *dev; 39 | }; 40 | 41 | struct devlink_region { 42 | union { 43 | uint8_t *u8; 44 | uint16_t *u16; 45 | } data; 46 | 47 | size_t size; 48 | }; 49 | 50 | static inline int devlink_region_loaded(struct devlink_region *region) 51 | { 52 | return region->size != 0; 53 | } 54 | 55 | static inline void devlink_region_free(struct devlink_region *region) 56 | { 57 | if (region->data.u8) 58 | free(region->data.u8); 59 | 60 | memset(region, 0, sizeof(*region)); 61 | } 62 | 63 | struct devlink { 64 | struct mnl_socket *nl; 65 | uint8_t *buf; 66 | 67 | uint32_t family; 68 | uint8_t version; 69 | 70 | unsigned int portid; 71 | unsigned int seq; 72 | }; 73 | 74 | 75 | struct nlmsghdr *devlink_msg_prepare(struct devlink *dl, uint8_t cmd, 76 | uint16_t flags); 77 | 78 | int devlink_attr_cb(const struct nlattr *attr, void *data); 79 | int devlink_parse(const struct nlmsghdr *nlh, struct nlattr **tb); 80 | int devlink_query(struct devlink *dl, struct nlmsghdr *nlh, 81 | mnl_cb_t cb, void *data); 82 | 83 | int devlink_region_dup_cb(const struct nlmsghdr *nlh, void *_region); 84 | int devlink_port_region_get(struct devlink *dl, struct devlink_addr *addr, 85 | int port, const char *name, 86 | mnl_cb_t cb, void *data); 87 | int devlink_region_get(struct devlink *dl, struct devlink_addr *addr, 88 | const char *name, mnl_cb_t cb, void *data); 89 | 90 | int devlink_open(struct devlink *dl); 91 | 92 | #endif /* __DEVLINK_H */ 93 | -------------------------------------------------------------------------------- /src/mvls/mvls-json.c: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "mvls.h" 9 | 10 | static const char *port_state[] = { "disabled", "blocking", "learning", "forwarding" }; 11 | static const char *port_fmode[] = { "normal", "dsa", "provider", "edsa" }; 12 | static const char *port_emode[] = { "unmodified", "untagged", "tagged", "edsa" }; 13 | static const char *port_8021q[] = { "disabled", "fallback", "check", "secure" }; 14 | 15 | static const char *atu_uc_str[] = { 16 | "unused", "age-1", "age-2", "age-3", 17 | "age-4", "age-5" , "age-6", "age-7", 18 | "policy", "policy-po", "nrl", "nrl-po", 19 | "mgmt", "mgmt-po", "static", "static-po" 20 | }; 21 | 22 | static const char *atu_mc_str[] = { 23 | "unused", "resvd1", "resvd2", "resvd3", 24 | "policy", "nrl", "mgmt", "static", 25 | "resvd8", "resvd9", "resvda", "resvdb", 26 | "policy-po", "nrl-po", "mgmt-po", "static-po" 27 | }; 28 | 29 | static void env_json_ports(struct env *env) 30 | { 31 | uint16_t ctrl, ctrl2; 32 | struct port *port; 33 | bool first = true; 34 | struct dev *dev; 35 | int lag; 36 | 37 | fputs("\"ports\":[", stdout); 38 | 39 | TAILQ_FOREACH(dev, &env->devs, node) { 40 | TAILQ_FOREACH(port, &dev->ports, node) { 41 | if (first) 42 | first = false; 43 | else 44 | putchar(','); 45 | 46 | port_load_regs(port); 47 | ctrl = reg16(port->regs, 4); 48 | ctrl2 = reg16(port->regs, 8); 49 | 50 | putchar('{'); 51 | 52 | printf("\"netdev\":\"%s\"," 53 | "\"dev\":%d," 54 | "\"port\":%d," 55 | "\"state\":\"%s\"," 56 | "\"frame-mode\":\"%s\"," 57 | "\"egress-mode\":\"%s\"," 58 | "\"802.1q\":\"%s\"," 59 | "\"pvid\":%u," 60 | "\"fid\":%u" 61 | , 62 | port->netdev, 63 | dev->index, 64 | port->index, 65 | port_state[bits(ctrl, 0, 2)], 66 | port_fmode[bits(ctrl, 8, 2)], 67 | port_emode[bits(ctrl, 12, 2)], 68 | port_8021q[bits(ctrl2, 10, 2)], 69 | bits(reg16(port->regs, 7), 0, 12), 70 | port_op(port, fid)); 71 | 72 | lag = port_op(port, lag); 73 | if (lag >= 0) 74 | printf(",\"lag\":%d", lag); 75 | 76 | if (bit(ctrl, 2)) 77 | fputs(",\"flood-unicast\":true", stdout); 78 | if (bit(ctrl, 3)) 79 | fputs(",\"flood-multicast\":true", stdout); 80 | 81 | if (bit(ctrl2, 8)) 82 | fputs(",\"allow-untagged\":true", stdout); 83 | if (bit(ctrl2, 9)) 84 | fputs(",\"allow-tagged\":true", stdout); 85 | 86 | putchar('}'); 87 | } 88 | } 89 | 90 | fputs("]", stdout); 91 | } 92 | 93 | static void json_print_portvec(const char *name, uint16_t portvec) 94 | { 95 | bool first = true; 96 | int i; 97 | 98 | printf(",\"%s\":[", name); 99 | 100 | for (i = 0; i < 11; i++) { 101 | if (!bit(portvec, i)) 102 | continue; 103 | 104 | if (first) 105 | first = false; 106 | else 107 | putchar(','); 108 | 109 | printf("%d", i); 110 | } 111 | 112 | putchar(']'); 113 | } 114 | 115 | static void env_json_atu(struct env *env) 116 | { 117 | struct mv88e6xxx_devlink_atu_entry *kentry; 118 | struct devlink_region atu = { 0 }; 119 | struct atu_entry entry; 120 | bool first = true; 121 | struct dev *dev; 122 | int err; 123 | 124 | fputs("\"atu\":[", stdout); 125 | 126 | TAILQ_FOREACH(dev, &env->devs, node) { 127 | err = devlink_region_get(&env->dl, &dev->devlink, "atu", 128 | devlink_region_dup_cb, &atu); 129 | if (err) { 130 | warn("failed querying atu"); 131 | break; 132 | } 133 | 134 | kentry = (void *)atu.data.u8; 135 | while (!dev_op(dev, atu_parse, kentry, &entry)) { 136 | if (first) 137 | first = false; 138 | else 139 | putchar(','); 140 | 141 | putchar('{'); 142 | 143 | printf("\"dev\":%d," 144 | "\"fid\":%u," 145 | "\"address\":\"%02x:%02x:%02x:%02x:%02x:%02x\"," 146 | "\"state\":\"%s\"" 147 | , 148 | dev->index, 149 | entry.fid, 150 | entry.addr[0], entry.addr[1], entry.addr[2], 151 | entry.addr[3], entry.addr[4], entry.addr[5], 152 | (entry.addr[0] & 1) ? 153 | atu_mc_str[entry.state.mc] : atu_uc_str[entry.state.uc]); 154 | 155 | if (entry.qpri.set) 156 | printf(",\"queue-prio-override\": %u", entry.qpri.pri); 157 | 158 | if (entry.fpri.set) 159 | printf(",\"frame-prio-override\": %u", entry.fpri.pri); 160 | 161 | if (entry.lag) { 162 | printf(",\"lag\": %u", entry.portvec); 163 | goto next; 164 | } 165 | 166 | json_print_portvec("ports", entry.portvec); 167 | 168 | next: 169 | putchar('}'); 170 | kentry++; 171 | } 172 | 173 | devlink_region_free(&atu); 174 | } 175 | 176 | fputs("]", stdout); 177 | } 178 | 179 | void env_json_vtu(struct env *env) 180 | { 181 | struct mv88e6xxx_devlink_vtu_entry *kentry; 182 | uint16_t unmod = 0, untag = 0, tag = 0; 183 | struct devlink_region vtu = { 0 }; 184 | struct vtu_entry entry; 185 | bool first = true; 186 | struct dev *dev; 187 | int err, i; 188 | 189 | fputs("\"vtu\":[", stdout); 190 | 191 | TAILQ_FOREACH(dev, &env->devs, node) { 192 | err = devlink_region_get(&env->dl, &dev->devlink, "vtu", 193 | devlink_region_dup_cb, &vtu); 194 | if (err) { 195 | warn("failed querying vtu"); 196 | break; 197 | } 198 | 199 | kentry = (void *)vtu.data.u8; 200 | while (!dev_op(dev, vtu_parse, kentry, &entry)) { 201 | if (first) 202 | first = false; 203 | else 204 | putchar(','); 205 | 206 | putchar('{'); 207 | 208 | printf("\"dev\":%d," 209 | "\"vid\":%u," 210 | "\"fid\":%u," 211 | "\"sid\":%u" 212 | , 213 | dev->index, 214 | entry.vid, entry.fid, entry.sid); 215 | 216 | if (entry.policy) 217 | fputs(",\"policy\": true", stdout); 218 | 219 | if (entry.qpri.set) 220 | printf(",\"queue-prio-override\": %u", entry.qpri.pri); 221 | 222 | if (entry.fpri.set) 223 | printf(",\"frame-prio-override\": %u", entry.fpri.pri); 224 | 225 | for (i = 0; i < 11; i++) { 226 | switch (entry.member[i]) { 227 | case VTU_UNMODIFIED: 228 | unmod |= (1 << i); 229 | break; 230 | case VTU_UNTAGGED: 231 | untag |= (1 << i); 232 | break; 233 | case VTU_TAGGED: 234 | tag |= (1 << i); 235 | break; 236 | case VTU_NOT_MEMBER: 237 | break; 238 | } 239 | } 240 | 241 | json_print_portvec("unmodified", unmod); 242 | json_print_portvec("untagged", untag); 243 | json_print_portvec("tagged", tag); 244 | 245 | putchar('}'); 246 | kentry++; 247 | } 248 | 249 | devlink_region_free(&vtu); 250 | } 251 | 252 | fputs("]", stdout); 253 | } 254 | 255 | void env_json_stu(struct env *env) 256 | { 257 | uint16_t dis = 0, blk = 0, lrn = 0, fwd = 0; 258 | struct mv88e6xxx_devlink_stu_entry *kentry; 259 | struct devlink_region stu = { 0 }; 260 | struct stu_entry entry; 261 | bool first = true; 262 | struct dev *dev; 263 | int err, i; 264 | 265 | fputs("\"stu\":[", stdout); 266 | 267 | TAILQ_FOREACH(dev, &env->devs, node) { 268 | err = devlink_region_get(&env->dl, &dev->devlink, "stu", 269 | devlink_region_dup_cb, &stu); 270 | if (err) { 271 | warn("failed querying stu"); 272 | break; 273 | } 274 | 275 | kentry = (void *)stu.data.u8; 276 | while (!dev_op(dev, stu_parse, kentry, &entry)) { 277 | if (first) 278 | first = false; 279 | else 280 | putchar(','); 281 | 282 | putchar('{'); 283 | 284 | printf("\"dev\":%d," 285 | "\"sid\":%u" 286 | , 287 | dev->index, 288 | entry.sid); 289 | 290 | for (i = 0; i < 11; i++) { 291 | switch (entry.state[i]) { 292 | case STU_DISABLED: 293 | dis |= (1 << i); 294 | break; 295 | case STU_BLOCKING: 296 | blk |= (1 << i); 297 | break; 298 | case STU_LEARNING: 299 | lrn |= (1 << i); 300 | break; 301 | case STU_FORWARDING: 302 | fwd |= (1 << i); 303 | break; 304 | } 305 | } 306 | 307 | json_print_portvec("disabled", dis); 308 | json_print_portvec("blocking", blk); 309 | json_print_portvec("learning", lrn); 310 | json_print_portvec("forwarding", fwd); 311 | 312 | putchar('}'); 313 | kentry++; 314 | } 315 | 316 | devlink_region_free(&stu); 317 | } 318 | 319 | fputs("]", stdout); 320 | } 321 | 322 | void json_prologue(void) 323 | { 324 | putchar('{'); 325 | } 326 | 327 | void json_join(void) 328 | { 329 | putchar(','); 330 | } 331 | 332 | void json_epilogue(void) 333 | { 334 | putchar('}'); 335 | } 336 | 337 | const struct printer printer_json = { 338 | .env_print_ports = env_json_ports, 339 | .env_print_vtu = env_json_vtu, 340 | .env_print_atu = env_json_atu, 341 | .env_print_stu = env_json_stu, 342 | /* .env_print_pvt = env_json_pvt, */ 343 | /* .dev_print_pvt = dev_json_pvt, */ 344 | 345 | .prologue = json_prologue, 346 | .join = json_join, 347 | .epilogue = json_epilogue, 348 | }; 349 | -------------------------------------------------------------------------------- /src/mvls/mvls-show.c: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "mvls.h" 8 | 9 | static char prio_c(struct prio_override prio) 10 | { 11 | if (!prio.set) 12 | return '-'; 13 | 14 | return '0' + prio.pri; 15 | } 16 | 17 | const char *atu_uc_str[] = { 18 | "UNUSED", "age 1", "age 2", "age 3", 19 | "age 4", "age 5" , "age 6", "age 7", 20 | "policy", "policy-po", "nrl", "nrl-po", 21 | "mgmt", "mgmt-po", "static", "static-po" 22 | }; 23 | 24 | const char *atu_mc_str[] = { 25 | "UNUSED", "resvd1", "resvd2", "resvd3", 26 | "policy", "nrl", "mgmt", "static", 27 | "resvd8", "resvd9", "resvda", "resvdb", 28 | "policy-po", "nrl-po", "mgmt-po", "static-po" 29 | }; 30 | 31 | char port_fmode_c[] = { 'n', 'D', 'P', 'E' }; 32 | char port_emode_c[] = { '=', 'u', 't', 'D' }; 33 | char port_state_c[] = { '-', 'B', 'L', 'f' }; 34 | char port_qmode_c[] = { '-', 'F', 'C', 's' }; 35 | 36 | static const char *port_link_str(struct port *port) 37 | { 38 | uint16_t stat = reg16(port->regs, 0); 39 | 40 | if (!bit(stat, 11)) 41 | return "-"; 42 | 43 | switch (bits(stat, 8, 2)) { 44 | case 0: 45 | return bit(stat, 10) ? "10" : "10h"; 46 | case 1: 47 | return bit(stat, 10) ? "100" : "100h"; 48 | case 2: 49 | return bit(stat, 10) ? "1G" : "1Gh"; 50 | case 3: 51 | return bit(stat, 10) ? "XG" : "XGh"; 52 | } 53 | 54 | return "NONE"; 55 | } 56 | 57 | static const char *port_lag_str(struct port *port) 58 | { 59 | static char str[] = "31"; 60 | 61 | int lag = port_op(port, lag); 62 | 63 | if (lag < 0) 64 | return "-"; 65 | 66 | snprintf(str, sizeof(str), "%d", lag & 0x1f); 67 | return str; 68 | } 69 | 70 | static void dev_print_portvec(struct dev *dev, uint16_t portvec) 71 | { 72 | int i; 73 | 74 | for (i = 0; i < 11; i++) { 75 | if (bit(portvec, i)) 76 | printf(" %x", i); 77 | else 78 | fputs(i >= dev->chip->n_ports ? 79 | " " : " .", stdout); 80 | } 81 | 82 | } 83 | 84 | static void dev_show_pvt(struct dev *dev) 85 | { 86 | struct port *port; 87 | int di, pi, err; 88 | 89 | puts("\e[7m D P 0 1 2 3 4 5 6 7 8 9 a\e[0m"); 90 | 91 | TAILQ_FOREACH(port, &dev->ports, node) { 92 | err = port_load_regs(port); 93 | if (err) { 94 | warn("failed querying ports"); 95 | return; 96 | } 97 | printf("%2x %x", dev->index, port->index); 98 | dev_print_portvec(dev, bits(reg16(port->regs, 6), 0, 11)); 99 | putchar('\n'); 100 | } 101 | 102 | err = dev_load_pvt(dev); 103 | if (err) { 104 | warn("failed querying pvt"); 105 | return; 106 | } 107 | 108 | for (di = 0; di < 32; di++) { 109 | for (pi = 0; pi < 16; pi++) { 110 | uint16_t portvec = reg16(dev->pvt, (di << 4) | pi); 111 | 112 | if (portvec) { 113 | printf("%2x %x", di, pi); 114 | dev_print_portvec(dev, bits(portvec, 0, 11)); 115 | putchar('\n'); 116 | } 117 | } 118 | } 119 | } 120 | 121 | void env_show_ports(struct env *env) 122 | { 123 | uint16_t ctrl, ctrl2; 124 | struct port *port; 125 | struct dev *dev; 126 | 127 | puts("\e[7mNETDEV P LINK MO FL S L .1Q PVID FID\e[0m"); 128 | 129 | TAILQ_FOREACH(dev, &env->devs, node) { 130 | printf("\e[2m\e[7mDEV:%x %-42s\e[0m\n", 131 | dev->index, dev->chip->id); 132 | 133 | TAILQ_FOREACH(port, &dev->ports, node) { 134 | port_load_regs(port); 135 | ctrl = reg16(port->regs, 4); 136 | ctrl2 = reg16(port->regs, 8); 137 | 138 | printf("%-8s %x %4s %c%c %c%c %c %2s %c%c%c %4u %4u%s\n", port->netdev, 139 | port->index, 140 | port_link_str(port), 141 | port_fmode_c[bits(ctrl, 8, 2)], 142 | port_emode_c[bits(ctrl, 12, 2)], 143 | bit(ctrl, 2) ? 'u' : '-', 144 | bit(ctrl, 3) ? 'm' : '-', 145 | port_state_c[bits(ctrl, 0, 2)], 146 | port_lag_str(port), 147 | port_qmode_c[bits(ctrl2, 10, 2)], 148 | bit(ctrl2, 8) ? '-' : 'u', 149 | bit(ctrl2, 9) ? '-' : 't', 150 | bits(reg16(port->regs, 7), 0, 12), 151 | port_op(port, fid), 152 | bit(ctrl2, 7) ? "" : "!map" 153 | ); 154 | } 155 | } 156 | } 157 | 158 | void env_show_atu(struct env *env) 159 | { 160 | struct dev *dev; 161 | struct devlink_region atu = { 0 }; 162 | struct mv88e6xxx_devlink_atu_entry *kentry; 163 | struct atu_entry entry; 164 | int err; 165 | 166 | puts("\e[7mADDRESS FID STATE Q F 0 1 2 3 4 5 6 7 8 9 a\e[0m"); 167 | 168 | TAILQ_FOREACH(dev, &env->devs, node) { 169 | printf("\e[2m\e[7mDEV:%x %-67s\e[0m\n", 170 | dev->index, dev->chip->id); 171 | 172 | err = devlink_region_get(&env->dl, &dev->devlink, "atu", 173 | devlink_region_dup_cb, &atu); 174 | if (err) { 175 | warn("failed querying atu"); 176 | break; 177 | } 178 | 179 | kentry = (void *)atu.data.u8; 180 | while (!dev_op(dev, atu_parse, kentry, &entry)) { 181 | printf("%02x:%02x:%02x:%02x:%02x:%02x %4u %-9s %c %c", 182 | entry.addr[0], entry.addr[1], entry.addr[2], 183 | entry.addr[3], entry.addr[4], entry.addr[5], 184 | entry.fid, (entry.addr[0] & 1) ? 185 | atu_mc_str[entry.state.mc] : atu_uc_str[entry.state.uc], 186 | prio_c(entry.qpri), prio_c(entry.fpri)); 187 | 188 | if (entry.lag) { 189 | printf(" lag %x", entry.portvec); 190 | goto next; 191 | } 192 | 193 | dev_print_portvec(dev, entry.portvec); 194 | 195 | next: 196 | putchar('\n'); 197 | kentry++; 198 | } 199 | 200 | devlink_region_free(&atu); 201 | } 202 | } 203 | 204 | void env_show_vtu(struct env *env) 205 | { 206 | struct dev *dev; 207 | struct devlink_region vtu = { 0 }; 208 | struct mv88e6xxx_devlink_vtu_entry *kentry; 209 | struct vtu_entry entry; 210 | int err, i; 211 | 212 | puts("\e[7m VID FID SID P Q F 0 1 2 3 4 5 6 7 8 9 a\e[0m"); 213 | 214 | TAILQ_FOREACH(dev, &env->devs, node) { 215 | printf("\e[2m\e[7mDEV:%x %-51s\e[0m\n", 216 | dev->index, dev->chip->id); 217 | 218 | err = devlink_region_get(&env->dl, &dev->devlink, "vtu", 219 | devlink_region_dup_cb, &vtu); 220 | if (err) { 221 | warn("failed querying vtu"); 222 | break; 223 | } 224 | 225 | kentry = (void *)vtu.data.u8; 226 | while (!dev_op(dev, vtu_parse, kentry, &entry)) { 227 | printf("%4u %4u %3u %c %c %c", 228 | entry.vid, entry.fid, entry.sid, 229 | entry.policy ? 'y' : '-', 230 | prio_c(entry.qpri), prio_c(entry.fpri)); 231 | 232 | for (i = 0; i < 11; i++) { 233 | switch (entry.member[i]) { 234 | case VTU_UNMODIFIED: 235 | fputs(i >= dev->chip->n_ports ? " " : " =", stdout); 236 | break; 237 | case VTU_UNTAGGED: 238 | fputs(" u", stdout); 239 | break; 240 | case VTU_TAGGED: 241 | fputs(" t", stdout); 242 | break; 243 | case VTU_NOT_MEMBER: 244 | fputs(" .", stdout); 245 | break; 246 | } 247 | } 248 | 249 | putchar('\n'); 250 | kentry++; 251 | } 252 | 253 | devlink_region_free(&vtu); 254 | } 255 | } 256 | 257 | void env_show_stu(struct env *env) 258 | { 259 | struct dev *dev; 260 | struct devlink_region stu = { 0 }; 261 | struct mv88e6xxx_devlink_stu_entry *kentry; 262 | struct stu_entry entry; 263 | int err, i; 264 | 265 | puts("\e[7mSID 0 1 2 3 4 5 6 7 8 9 a\e[0m"); 266 | 267 | TAILQ_FOREACH(dev, &env->devs, node) { 268 | printf("\e[2m\e[7mDEV:%x %-30s\e[0m\n", 269 | dev->index, dev->chip->id); 270 | 271 | err = devlink_region_get(&env->dl, &dev->devlink, "stu", 272 | devlink_region_dup_cb, &stu); 273 | if (err) { 274 | warn("failed querying stu"); 275 | break; 276 | } 277 | 278 | kentry = (void *)stu.data.u8; 279 | while (!dev_op(dev, stu_parse, kentry, &entry)) { 280 | printf("%3u", entry.sid); 281 | 282 | for (i = 0; i < 11; i++) { 283 | switch (entry.state[i]) { 284 | case STU_DISABLED: 285 | fputs(i >= dev->chip->n_ports ? " " : " -", stdout); 286 | break; 287 | default: 288 | printf(" %c", port_state_c[entry.state[i]]); 289 | } 290 | } 291 | 292 | putchar('\n'); 293 | kentry++; 294 | } 295 | 296 | devlink_region_free(&stu); 297 | } 298 | } 299 | 300 | void pvt_print_cell(uint16_t spvt, uint16_t dpvt, int src, int dst) 301 | { 302 | if (bit(spvt, dst) && bit(dpvt, src)) 303 | fputs(" x", stdout); 304 | else if (bit(spvt, dst)) 305 | fputs(" ^", stdout); 306 | else if (bit(dpvt, src)) 307 | fputs(" <", stdout); 308 | else 309 | fputs(" .", stdout); 310 | 311 | } 312 | 313 | void env_show_pvt_port(struct port *src, unsigned lags) 314 | { 315 | struct env *env = src->dev->env; 316 | uint16_t spvt, dpvt; 317 | struct port *port; 318 | struct dev *dev; 319 | int lag; 320 | 321 | printf("\e[7m%x %x\e[0m", src->dev->index, src->index); 322 | 323 | TAILQ_FOREACH(dev, &env->devs, node) { 324 | TAILQ_FOREACH(port, &dev->ports, node) { 325 | if (port->dev == src->dev) { 326 | spvt = reg16(src->regs, 6); 327 | dpvt = reg16(port->regs, 6); 328 | } else { 329 | spvt = reg16(dev->pvt, (src->dev->index << 4) + src->index); 330 | dpvt = reg16(src->dev->pvt, (port->dev->index << 4) + port->index); 331 | } 332 | 333 | pvt_print_cell(spvt, dpvt, src->index, port->index); 334 | } 335 | 336 | if (TAILQ_NEXT(dev, node) || lags) 337 | putchar(' '); 338 | } 339 | 340 | for (lag = 0; lag < 16; lag++) { 341 | if (!(lags & (1 << lag))) 342 | continue; 343 | 344 | dpvt = reg16(src->dev->pvt, (0x1f << 4) + lag); 345 | pvt_print_cell(0, dpvt, src->index, 0); 346 | } 347 | 348 | putchar('\n'); 349 | } 350 | 351 | void env_show_pvt(struct env *env) 352 | { 353 | struct dev *dev; 354 | struct port *port; 355 | unsigned lags = 0; 356 | int err, lag; 357 | 358 | fputs("\e[7mD ", stdout); 359 | TAILQ_FOREACH(dev, &env->devs, node) { 360 | err = dev_load_pvt(dev); 361 | if (err) { 362 | warn("failed querying pvt"); 363 | return; 364 | } 365 | 366 | TAILQ_FOREACH(port, &dev->ports, node) { 367 | err = port_load_regs(port); 368 | if (err) { 369 | warn("failed querying ports"); 370 | return; 371 | } 372 | 373 | lag = port_op(port, lag); 374 | if (lag >= 0) 375 | lags |= 1 << lag; 376 | 377 | printf(" %x", dev->index); 378 | } 379 | 380 | if (TAILQ_NEXT(dev, node)) 381 | putchar(' '); 382 | } 383 | 384 | if (lags) 385 | putchar(' '); 386 | 387 | for (lag = 0; lag < 16; lag++) 388 | if (lags & (1 << lag)) 389 | fputs(" L", stdout); 390 | 391 | fputs("\n P", stdout); 392 | TAILQ_FOREACH(dev, &env->devs, node) { 393 | TAILQ_FOREACH(port, &dev->ports, node) 394 | printf(" %x", port->index); 395 | 396 | if (TAILQ_NEXT(dev, node) || lags) 397 | putchar(' '); 398 | } 399 | 400 | for (lag = 0; lag < 16; lag++) 401 | if (lags & (1 << lag)) 402 | printf(" %x", lag); 403 | 404 | puts("\e[0m"); 405 | 406 | TAILQ_FOREACH(dev, &env->devs, node) { 407 | TAILQ_FOREACH(port, &dev->ports, node) { 408 | env_show_pvt_port(port, lags); 409 | } 410 | 411 | if (TAILQ_NEXT(dev, node)) 412 | puts("\e[7m \e[0m"); 413 | } 414 | } 415 | 416 | void show_join(void) 417 | { 418 | putchar('\n'); 419 | } 420 | 421 | const struct printer printer_show = { 422 | .env_print_ports = env_show_ports, 423 | .env_print_vtu = env_show_vtu, 424 | .env_print_atu = env_show_atu, 425 | .env_print_stu = env_show_stu, 426 | .env_print_pvt = env_show_pvt, 427 | .dev_print_pvt = dev_show_pvt, 428 | 429 | .join = show_join, 430 | }; 431 | -------------------------------------------------------------------------------- /src/mvls/mvls.c: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "devlink.h" 10 | #include "mvls.h" 11 | 12 | static int __dev_vtu_parse(struct dev *dev, 13 | struct mv88e6xxx_devlink_vtu_entry *kentry, 14 | struct vtu_entry *entry, uint8_t mbits) 15 | { 16 | int i; 17 | 18 | if (!bit(kentry->vid, 12)) { 19 | errno = ENODATA; 20 | return -1; 21 | } 22 | 23 | entry->vid = bits(kentry->vid, 0, 12); 24 | entry->fid = bits(kentry->fid, 0, 12); 25 | entry->sid = bits(kentry->sid, 0, 6); 26 | 27 | for (i = 0; i < 11; i++) 28 | entry->member[i] = bits(kentry->data[i / (16 / mbits)], 29 | (i * mbits) & 0xf, 2); 30 | 31 | entry->policy = bit(kentry->fid, 12); 32 | 33 | /* TODO */ 34 | entry->qpri.set = 0; 35 | entry->fpri.set = 0; 36 | return 0; 37 | } 38 | 39 | static int peridot_dev_vtu_parse(struct dev *dev, 40 | struct mv88e6xxx_devlink_vtu_entry *kentry, 41 | struct vtu_entry *entry) 42 | { 43 | return __dev_vtu_parse(dev, kentry, entry, 2); 44 | } 45 | 46 | static int opal_dev_vtu_parse(struct dev *dev, 47 | struct mv88e6xxx_devlink_vtu_entry *kentry, 48 | struct vtu_entry *entry) 49 | { 50 | return __dev_vtu_parse(dev, kentry, entry, 4); 51 | } 52 | 53 | static int __dev_stu_parse(struct dev *dev, 54 | struct mv88e6xxx_devlink_stu_entry *kentry, 55 | struct stu_entry *entry, uint8_t mbits) 56 | { 57 | uint8_t offs = (mbits == 4) ? 2 : 0; 58 | int i; 59 | 60 | if (!bit(kentry->vid, 12)) { 61 | errno = ENODATA; 62 | return -1; 63 | } 64 | 65 | entry->sid = bits(kentry->sid, 0, 6); 66 | 67 | for (i = 0; i < 11; i++) 68 | entry->state[i] = bits(kentry->data[i / (16 / mbits)], 69 | (i * mbits + offs) & 0xf, 2); 70 | return 0; 71 | } 72 | 73 | static int peridot_dev_stu_parse(struct dev *dev, 74 | struct mv88e6xxx_devlink_stu_entry *kentry, 75 | struct stu_entry *entry) 76 | { 77 | return __dev_stu_parse(dev, kentry, entry, 2); 78 | } 79 | 80 | static int opal_dev_stu_parse(struct dev *dev, 81 | struct mv88e6xxx_devlink_stu_entry *kentry, 82 | struct stu_entry *entry) 83 | { 84 | return __dev_stu_parse(dev, kentry, entry, 4); 85 | } 86 | 87 | static int prio_override_state(bool mc, int state) 88 | { 89 | if ((mc) && 90 | ((state == ATU_MC_PCY_PO) || 91 | (state == ATU_MC_NRL_PO) || 92 | (state == ATU_MC_MGMT_PO) || 93 | (state == ATU_MC_PO))) 94 | return 1; 95 | else if ((state == ATU_UC_S_PCY_PO) || 96 | (state == ATU_UC_S_NRL_PO) || 97 | (state == ATU_UC_S_MGMT_PO) || 98 | (state == ATU_UC_S_PO)) 99 | return 1; 100 | 101 | return 0; 102 | } 103 | 104 | int __dev_atu_parse(struct dev *dev, 105 | struct mv88e6xxx_devlink_atu_entry *kentry, 106 | struct atu_entry *entry, bool have_fpri) 107 | { 108 | int set_prio; 109 | 110 | if (!bits(kentry->atu_data, 0, 4)) { 111 | errno = ENODATA; 112 | return -1; 113 | } 114 | 115 | entry->fid = kentry->fid; 116 | entry->addr[0] = bits(kentry->atu_01, 8, 8); 117 | entry->addr[1] = bits(kentry->atu_01, 0, 8); 118 | entry->addr[2] = bits(kentry->atu_23, 8, 8); 119 | entry->addr[3] = bits(kentry->atu_23, 0, 8); 120 | entry->addr[4] = bits(kentry->atu_45, 8, 8); 121 | entry->addr[5] = bits(kentry->atu_45, 0, 8); 122 | 123 | entry->lag = bit(kentry->atu_data, 15); 124 | entry->portvec = bits(kentry->atu_data, 4, 11); 125 | entry->state.uc = bits(kentry->atu_data, 0, 4); 126 | 127 | set_prio = prio_override_state((entry->addr[0] & 1), entry->state.uc); 128 | entry->qpri.set = set_prio; 129 | entry->qpri.pri = set_prio ? bits(kentry->atu_op, 8, 3) : 0; 130 | entry->fpri.set = have_fpri && set_prio; 131 | entry->fpri.pri = (have_fpri && set_prio) ? bits(kentry->atu_op, 0, 3) : 0; 132 | return 0; 133 | } 134 | 135 | int opal_dev_atu_parse(struct dev *dev, 136 | struct mv88e6xxx_devlink_atu_entry *kentry, 137 | struct atu_entry *entry) 138 | { 139 | return __dev_atu_parse(dev, kentry, entry, false); 140 | } 141 | 142 | int peridot_dev_atu_parse(struct dev *dev, 143 | struct mv88e6xxx_devlink_atu_entry *kentry, 144 | struct atu_entry *entry) 145 | { 146 | return __dev_atu_parse(dev, kentry, entry, true); 147 | } 148 | 149 | int opal_port_lag(struct port *port) 150 | { 151 | uint16_t ctrl1 = reg16(port->regs, 5); 152 | 153 | if (!bit(ctrl1, 14)) 154 | return -1; 155 | 156 | return bits(ctrl1, 8, 4); 157 | } 158 | 159 | uint16_t opal_port_fid(struct port *port) 160 | { 161 | return bits(reg16(port->regs, 6), 12, 4) | 162 | (bits(reg16(port->regs, 5), 0, 8) << 4); 163 | } 164 | 165 | const struct family opal_family = { 166 | .port_lag = opal_port_lag, 167 | .port_fid = opal_port_fid, 168 | 169 | .dev_atu_parse = opal_dev_atu_parse, 170 | .dev_vtu_parse = opal_dev_vtu_parse, 171 | .dev_stu_parse = opal_dev_stu_parse, 172 | }; 173 | 174 | const struct family peridot_family = { 175 | .port_lag = opal_port_lag, 176 | .port_fid = opal_port_fid, 177 | 178 | .dev_atu_parse = peridot_dev_atu_parse, 179 | .dev_vtu_parse = peridot_dev_vtu_parse, 180 | .dev_stu_parse = peridot_dev_stu_parse, 181 | }; 182 | 183 | const struct family amethyst_family = { 184 | .port_lag = opal_port_lag, 185 | .port_fid = opal_port_fid, 186 | 187 | .dev_atu_parse = peridot_dev_atu_parse, 188 | .dev_vtu_parse = peridot_dev_vtu_parse, 189 | .dev_stu_parse = peridot_dev_stu_parse, 190 | }; 191 | 192 | const struct chip chips[] = { 193 | { 194 | .id = "Marvell 88E6097/88E6097F", 195 | .family = &opal_family, 196 | .n_ports = 11, 197 | }, 198 | { 199 | .id = "Marvell 88E6352", 200 | .family = &opal_family, 201 | .n_ports = 7, 202 | }, 203 | { 204 | .id = "Marvell 88E6320", 205 | .family = &opal_family, 206 | .n_ports = 7, 207 | }, 208 | { 209 | .id = "Marvell 88E6321", 210 | .family = &opal_family, 211 | .n_ports = 7, 212 | }, 213 | { 214 | .id = "Marvell 88E6190", 215 | .family = &peridot_family, 216 | .n_ports = 11, 217 | }, 218 | { 219 | .id = "Marvell 88E6190X", 220 | .family = &peridot_family, 221 | .n_ports = 11, 222 | }, 223 | { 224 | .id = "Marvell 88E6191", 225 | .family = &peridot_family, 226 | .n_ports = 11, 227 | }, 228 | { 229 | .id = "Marvell 88E6191X", 230 | .family = &amethyst_family, 231 | .n_ports = 11, 232 | }, 233 | { 234 | .id = "Marvell 88E6193X", 235 | .family = &amethyst_family, 236 | .n_ports = 11, 237 | }, 238 | { 239 | .id = "Marvell 88E6290", 240 | .family = &peridot_family, 241 | .n_ports = 11, 242 | }, 243 | { 244 | .id = "Marvell 88E6390", 245 | .family = &peridot_family, 246 | .n_ports = 11, 247 | }, 248 | { 249 | .id = "Marvell 88E6390X", 250 | .family = &peridot_family, 251 | .n_ports = 11, 252 | }, 253 | { 254 | .id = "Marvell 88E6393X", 255 | .family = &amethyst_family, 256 | .n_ports = 11, 257 | }, 258 | 259 | { .id = NULL } 260 | }; 261 | 262 | int port_load_regs(struct port *port) 263 | { 264 | struct dev *dev = port->dev; 265 | struct env *env = dev->env; 266 | 267 | if (devlink_region_loaded(&port->regs)) 268 | return 0; 269 | 270 | return devlink_port_region_get(&env->dl, &dev->devlink, port->index, 271 | "port", devlink_region_dup_cb, &port->regs); 272 | } 273 | 274 | int dev_load_pvt(struct dev *dev) 275 | { 276 | if (devlink_region_loaded(&dev->pvt)) 277 | return 0; 278 | 279 | return devlink_region_get(&dev->env->dl, &dev->devlink, 280 | "pvt", devlink_region_dup_cb, &dev->pvt); 281 | } 282 | 283 | 284 | static int dev_init(struct dev *dev) 285 | { 286 | int err; 287 | 288 | err = devlink_region_get(&dev->env->dl, &dev->devlink, 289 | "global1", devlink_region_dup_cb, &dev->global1); 290 | if (err) 291 | return err; 292 | 293 | dev->index = bits(reg16(dev->global1, 0x1c), 0, 5); 294 | return 0; 295 | } 296 | 297 | 298 | static struct dev *env_dev_get(struct env *env, int index) 299 | { 300 | struct dev *dev; 301 | 302 | TAILQ_FOREACH(dev, &env->devs, node) { 303 | if (dev->index == index) 304 | return dev; 305 | } 306 | 307 | errno = EINVAL; 308 | return NULL; 309 | } 310 | 311 | static struct dev *env_dev_find(struct env *env, 312 | const char *busid, const char *devid) 313 | { 314 | struct dev *dev; 315 | 316 | TAILQ_FOREACH(dev, &env->devs, node) { 317 | if (strcmp(dev->devlink.bus, busid)) 318 | continue; 319 | 320 | if (strcmp(dev->devlink.dev, devid)) 321 | continue; 322 | 323 | return dev; 324 | } 325 | 326 | errno = ENODEV; 327 | return NULL; 328 | } 329 | 330 | static int env_init_port_cb(const struct nlmsghdr *nlh, void *_env) 331 | { 332 | struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {}; 333 | struct env *env = _env; 334 | struct port *port; 335 | struct dev *dev; 336 | 337 | devlink_parse(nlh, tb); 338 | if (!tb[DEVLINK_ATTR_BUS_NAME] || 339 | !tb[DEVLINK_ATTR_DEV_NAME] || 340 | !tb[DEVLINK_ATTR_PORT_INDEX] || 341 | !tb[DEVLINK_ATTR_PORT_FLAVOUR]) 342 | return MNL_CB_ERROR; 343 | 344 | dev = env_dev_find(env, 345 | mnl_attr_get_str(tb[DEVLINK_ATTR_BUS_NAME]), 346 | mnl_attr_get_str(tb[DEVLINK_ATTR_DEV_NAME])); 347 | 348 | if (!dev) 349 | return MNL_CB_OK; 350 | 351 | port = calloc(1, sizeof(*port)); 352 | if (!port) 353 | return MNL_CB_ERROR; 354 | 355 | port->dev = dev; 356 | port->index = mnl_attr_get_u32(tb[DEVLINK_ATTR_PORT_INDEX]); 357 | port->flavor = mnl_attr_get_u16(tb[DEVLINK_ATTR_PORT_FLAVOUR]); 358 | 359 | if (tb[DEVLINK_ATTR_PORT_NETDEV_NAME]) 360 | port->netdev = strdup(mnl_attr_get_str(tb[DEVLINK_ATTR_PORT_NETDEV_NAME])); 361 | else { 362 | switch (port->flavor) { 363 | case DEVLINK_PORT_FLAVOUR_CPU: 364 | port->netdev = strdup("(cpu)"); 365 | break; 366 | case DEVLINK_PORT_FLAVOUR_DSA: 367 | port->netdev = strdup("(dsa)"); 368 | break; 369 | default: 370 | port->netdev = strdup("-"); 371 | break; 372 | } 373 | } 374 | 375 | TAILQ_INSERT_TAIL(&dev->ports, port, node); 376 | return MNL_CB_OK; 377 | } 378 | 379 | static int env_dev_add(struct env *env, const char *asicid, 380 | const char *busid, const char *devid) 381 | { 382 | const struct chip *chip; 383 | struct dev *dev; 384 | 385 | for (chip = chips; chip->id; chip++) { 386 | if (!strcmp(chip->id, asicid)) 387 | break; 388 | } 389 | 390 | if (!chip->id) 391 | return ENOSYS; 392 | 393 | dev = calloc(1, sizeof(*dev)); 394 | if (!dev) 395 | return ENOMEM; 396 | 397 | dev->env = env; 398 | dev->devlink.bus = strdup(busid); 399 | dev->devlink.dev = strdup(devid); 400 | dev->chip = chip; 401 | TAILQ_INIT(&dev->ports); 402 | 403 | if (TAILQ_FIRST(&env->devs)) 404 | env->multichip = true; 405 | 406 | TAILQ_INSERT_TAIL(&env->devs, dev, node); 407 | return 0; 408 | } 409 | 410 | static int env_init_dev_cb(const struct nlmsghdr *nlh, void *_env) 411 | { 412 | struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {}; 413 | struct env *env = _env; 414 | const char *id = NULL; 415 | struct nlattr *ver; 416 | int err; 417 | 418 | devlink_parse(nlh, tb); 419 | if (!tb[DEVLINK_ATTR_BUS_NAME] || 420 | !tb[DEVLINK_ATTR_DEV_NAME] || 421 | !tb[DEVLINK_ATTR_INFO_DRIVER_NAME]) 422 | return MNL_CB_ERROR; 423 | 424 | if (strncmp("mv88e6", 425 | mnl_attr_get_str(tb[DEVLINK_ATTR_INFO_DRIVER_NAME]), 6)) 426 | return MNL_CB_OK; 427 | 428 | mnl_attr_for_each(ver, nlh, sizeof(struct genlmsghdr)) { 429 | if (mnl_attr_get_type(ver) != DEVLINK_ATTR_INFO_VERSION_FIXED) 430 | continue; 431 | 432 | err = mnl_attr_parse_nested(ver, devlink_attr_cb, tb); 433 | if (err != MNL_CB_OK) 434 | continue; 435 | 436 | if (!tb[DEVLINK_ATTR_INFO_VERSION_NAME] || 437 | !tb[DEVLINK_ATTR_INFO_VERSION_VALUE]) 438 | continue; 439 | 440 | if (strcmp("asic.id", mnl_attr_get_str(tb[DEVLINK_ATTR_INFO_VERSION_NAME]))) 441 | continue; 442 | 443 | id = mnl_attr_get_str(tb[DEVLINK_ATTR_INFO_VERSION_VALUE]); 444 | break; 445 | } 446 | 447 | if (!id) 448 | return MNL_CB_OK; 449 | 450 | err = env_dev_add(env, id, 451 | mnl_attr_get_str(tb[DEVLINK_ATTR_BUS_NAME]), 452 | mnl_attr_get_str(tb[DEVLINK_ATTR_DEV_NAME])); 453 | if (err) 454 | return MNL_CB_ERROR; 455 | 456 | return MNL_CB_OK; 457 | } 458 | 459 | void env_init_dev_sort(struct env *env) 460 | { 461 | struct dev *dev, *next, *insert; 462 | 463 | /* Sort devices on DSA index. Yes, use bubblesort. This list 464 | * can contain a maximum of 32 devs, typically 1-3. */ 465 | for (dev = TAILQ_FIRST(&env->devs); dev; dev = next) { 466 | next = TAILQ_NEXT(dev, node); 467 | if (!next) 468 | break; 469 | 470 | if (next->index > dev->index) 471 | continue; 472 | 473 | for (insert = next; insert && (insert->index < dev->index); 474 | insert = TAILQ_NEXT(insert, node)); 475 | 476 | TAILQ_REMOVE(&env->devs, dev, node); 477 | 478 | if (insert) 479 | TAILQ_INSERT_BEFORE(insert, dev, node); 480 | else 481 | TAILQ_INSERT_TAIL(&env->devs, dev, node); 482 | } 483 | 484 | } 485 | 486 | int env_init(struct env *env) 487 | { 488 | struct nlmsghdr *nlh; 489 | struct dev *dev; 490 | int err; 491 | 492 | TAILQ_INIT(&env->devs); 493 | 494 | err = devlink_open(&env->dl); 495 | if (err) 496 | return err; 497 | 498 | nlh = devlink_msg_prepare(&env->dl, DEVLINK_CMD_INFO_GET, 499 | NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP); 500 | 501 | err = devlink_query(&env->dl, nlh, env_init_dev_cb, env); 502 | if (err) 503 | return err; 504 | 505 | nlh = devlink_msg_prepare(&env->dl, DEVLINK_CMD_PORT_GET, 506 | NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP); 507 | 508 | err = devlink_query(&env->dl, nlh, env_init_port_cb, env); 509 | if (err) 510 | return err; 511 | 512 | if (TAILQ_EMPTY(&env->devs)) { 513 | errno = ENODEV; 514 | return -1; 515 | } 516 | 517 | TAILQ_FOREACH(dev, &env->devs, node) { 518 | err = dev_init(dev); 519 | if (err) 520 | return err; 521 | } 522 | 523 | env_init_dev_sort(env); 524 | return err; 525 | } 526 | 527 | 528 | int usage(int rc) 529 | { 530 | fputs("Usage: mvls [OPT] [CMD]\n" 531 | "\n" 532 | "Options:\n" 533 | " -h This help text\n" 534 | " -j Use JSON for all output\n" 535 | " -v Show verision and contact information\n" 536 | "\n" 537 | "Commands:\n" 538 | " port\n" 539 | " Displays and overview of switchcore ports and their properties.\n" 540 | "\n" 541 | " atu\n" 542 | " Displays the contents of the ATU with VLAN and port vectors.\n" 543 | "\n" 544 | " vtu\n" 545 | " Displays the contents of the VTU with FID and port mappings.\n" 546 | " VLAN membership states:\n" 547 | " . Not a member\n" 548 | " u Member, egress untagged\n" 549 | " t Member, egress tagged\n" 550 | " = Member, egress unmodified\n" 551 | "\n" 552 | " stu\n" 553 | " Displays the contents of the STU.\n" 554 | " STU states:\n" 555 | " - Disabled\n" 556 | " B Blocking/listening\n" 557 | " L Learning\n" 558 | " f Forwarding\n" 559 | "\n" 560 | " pvt [DEV]\n" 561 | "" 562 | " Displays the contents of the Port VLAN Table. Without DEV, a\n" 563 | " condensed view of the isolation state between all ports is shown:\n" 564 | " . Full isolation\n" 565 | " x Bidirectional communication allowed\n" 566 | " < Only communication from column to row port allowed\n" 567 | " ^ Only communication from row to column port allowed\n" 568 | "\n" 569 | " If DEV is supplied, dump the full PVT for that device.\n" 570 | "\n" 571 | "By default, mvls displays an overview of the VTU, ATU and ports.\n" 572 | , stdout); 573 | 574 | return rc; 575 | } 576 | 577 | int main(int argc, char **argv) 578 | { 579 | const struct printer *p = &printer_show; 580 | struct env env; 581 | int c; 582 | 583 | while ((c = getopt(argc, argv, "hjv")) != EOF) { 584 | switch (c) { 585 | case 'h': 586 | return usage(0); 587 | 588 | case 'j': 589 | p = &printer_json; 590 | break; 591 | 592 | case 'v': 593 | puts("v" PACKAGE_VERSION); 594 | puts("\nBug report address: " PACKAGE_BUGREPORT); 595 | return 0; 596 | 597 | default: 598 | return usage(1); 599 | } 600 | } 601 | 602 | if (env_init(&env)) 603 | err(1, "failed discovering any devices"); 604 | 605 | print_prologue(p); 606 | 607 | if (optind == argc) { 608 | env_print_vtu(p, &env); 609 | print_join(p); 610 | env_print_stu(p, &env); 611 | print_join(p); 612 | env_print_atu(p, &env); 613 | print_join(p); 614 | env_print_ports(p, &env); 615 | goto epilogue; 616 | } 617 | 618 | if (!strcmp(argv[optind], "port")) 619 | env_print_ports(p, &env); 620 | if (!strcmp(argv[optind], "atu")) 621 | env_print_atu(p, &env); 622 | if (!strcmp(argv[optind], "vtu")) 623 | env_print_vtu(p, &env); 624 | if (!strcmp(argv[optind], "stu")) 625 | env_print_stu(p, &env); 626 | if (!strcmp(argv[optind], "pvt")) { 627 | struct dev *dev; 628 | int index; 629 | 630 | if (++optind == argc) { 631 | env_print_pvt(p, &env); 632 | goto epilogue; 633 | } 634 | 635 | index = strtol(argv[optind], NULL, 0); 636 | dev = env_dev_get(&env, index); 637 | if (!dev) 638 | err(1, "unknown device index \"%s\"", argv[optind]); 639 | 640 | dev_print_pvt(p, dev); 641 | } 642 | 643 | epilogue: 644 | print_epilogue(p); 645 | return 0; 646 | } 647 | -------------------------------------------------------------------------------- /src/mvls/mvls.h: -------------------------------------------------------------------------------- 1 | #ifndef __MVLS_H 2 | #define __MVLS_H 3 | 4 | #include 5 | 6 | #include "devlink.h" 7 | #include "queue.h" 8 | 9 | #define offsetof(type, member) __builtin_offsetof (type, member) 10 | #define container_of(ptr, type, member) ({ \ 11 | const typeof( ((type *)0)->member ) *__mptr = (ptr); \ 12 | (type *)( (char *)__mptr - offsetof(type,member) );}) 13 | 14 | #define reg16(_region, _reg) ((_region).data.u16[_reg]) 15 | #define bits(_reg, _bit, _n) (((_reg) >> (_bit)) & ((1 << (_n)) - 1)) 16 | #define bit(_reg, _bit) bits(_reg, _bit, 1) 17 | 18 | #define MAX_PORTS 11 19 | 20 | enum vtu_member { 21 | VTU_UNMODIFIED = 0, 22 | VTU_UNTAGGED = 1, 23 | VTU_TAGGED = 2, 24 | VTU_NOT_MEMBER = 3 25 | }; 26 | 27 | struct prio_override { 28 | uint8_t set:1; 29 | uint8_t pri:3; 30 | }; 31 | 32 | struct vtu_entry { 33 | uint16_t vid; 34 | uint16_t fid; 35 | uint8_t sid; 36 | 37 | enum vtu_member member[MAX_PORTS]; 38 | 39 | bool policy; 40 | 41 | struct prio_override qpri; 42 | struct prio_override fpri; 43 | }; 44 | 45 | enum stu_state { 46 | STU_DISABLED = 0, 47 | STU_BLOCKING = 1, 48 | STU_LEARNING = 2, 49 | STU_FORWARDING = 3 50 | }; 51 | 52 | struct stu_entry { 53 | uint8_t sid; 54 | 55 | enum stu_state state[MAX_PORTS]; 56 | }; 57 | 58 | enum atu_uc_state { 59 | ATU_UC_UNUSED = 0x0, 60 | ATU_UC_D_AGE_1 = 0x1, 61 | ATU_UC_D_AGE_2 = 0x2, 62 | ATU_UC_D_AGE_3 = 0x3, 63 | ATU_UC_D_AGE_4 = 0x4, 64 | ATU_UC_D_AGE_5 = 0x5, 65 | ATU_UC_D_AGE_6 = 0x6, 66 | ATU_UC_D_AGE_7 = 0x7, 67 | ATU_UC_S_PCY = 0x8, 68 | ATU_UC_S_PCY_PO = 0x9, 69 | ATU_UC_S_NRL = 0xa, 70 | ATU_UC_S_NRL_PO = 0xb, 71 | ATU_UC_S_MGMT = 0xc, 72 | ATU_UC_S_MGMT_PO = 0xd, 73 | ATU_UC_S = 0xe, 74 | ATU_UC_S_PO = 0xf 75 | }; 76 | 77 | enum atu_mc_state { 78 | ATU_MC_UNUSED = 0x0, 79 | ATU_MC_RESVD_1 = 0x1, 80 | ATU_MC_RESVD_2 = 0x2, 81 | ATU_MC_RESVD_3 = 0x3, 82 | ATU_MC_PCY = 0x4, 83 | ATU_MC_NRL = 0x5, 84 | ATU_MC_MGMT = 0x6, 85 | ATU_MC = 0x7, 86 | ATU_MC_RESVD_8 = 0x8, 87 | ATU_MC_RESVD_9 = 0x9, 88 | ATU_MC_RESVD_A = 0xa, 89 | ATU_MC_RESVD_B = 0xb, 90 | ATU_MC_PCY_PO = 0xc, 91 | ATU_MC_NRL_PO = 0xd, 92 | ATU_MC_MGMT_PO = 0xe, 93 | ATU_MC_PO = 0xf 94 | }; 95 | 96 | struct atu_entry { 97 | uint16_t fid; 98 | uint8_t addr[6]; 99 | 100 | struct prio_override qpri; 101 | struct prio_override fpri; 102 | 103 | bool lag; 104 | uint16_t portvec; 105 | 106 | union { 107 | enum atu_uc_state uc; 108 | enum atu_mc_state mc; 109 | } state; 110 | }; 111 | 112 | struct port; 113 | struct dev; 114 | 115 | struct family { 116 | int (*port_lag)(struct port *port); 117 | uint16_t (*port_fid)(struct port *port); 118 | 119 | int (*dev_atu_parse)(struct dev *dev, 120 | struct mv88e6xxx_devlink_atu_entry *kentry, 121 | struct atu_entry *entry); 122 | int (*dev_vtu_parse)(struct dev *dev, 123 | struct mv88e6xxx_devlink_vtu_entry *kentry, 124 | struct vtu_entry *entry); 125 | int (*dev_stu_parse)(struct dev *dev, 126 | struct mv88e6xxx_devlink_stu_entry *kentry, 127 | struct stu_entry *entry); 128 | }; 129 | 130 | struct chip { 131 | char *id; 132 | const struct family *family; 133 | 134 | int n_ports; 135 | }; 136 | 137 | struct port { 138 | struct dev *dev; 139 | int index; 140 | char *netdev; 141 | enum devlink_port_flavour flavor; 142 | 143 | struct devlink_region regs; 144 | 145 | TAILQ_ENTRY(port) node; 146 | }; 147 | TAILQ_HEAD(port_list, port); 148 | 149 | int port_load_regs(struct port *port); 150 | 151 | #define port_op(_port, _op, ...) \ 152 | ((_port)->dev->chip->family-> port_ ## _op )((_port), ##__VA_ARGS__) 153 | 154 | struct dev { 155 | struct env *env; 156 | struct devlink_addr devlink; 157 | const struct chip *chip; 158 | 159 | int index; 160 | struct port_list ports; 161 | 162 | struct devlink_region global1; 163 | struct devlink_region pvt; 164 | 165 | TAILQ_ENTRY(dev) node; 166 | }; 167 | TAILQ_HEAD(dev_list, dev); 168 | 169 | int dev_load_pvt(struct dev *dev); 170 | 171 | #define dev_op(_dev, _op, ...) \ 172 | ((_dev)->chip->family-> dev_ ## _op )((_dev), ##__VA_ARGS__) 173 | 174 | struct env { 175 | bool multichip:1; 176 | 177 | struct devlink dl; 178 | struct dev_list devs; 179 | }; 180 | 181 | #define env_from_dl(_dl) container_of(_dl, struct env, dl) 182 | 183 | struct printer { 184 | void (*env_print_ports)(struct env *env); 185 | void (*env_print_atu)(struct env *env); 186 | void (*env_print_vtu)(struct env *env); 187 | void (*env_print_stu)(struct env *env); 188 | 189 | void (*env_print_pvt)(struct env *env); 190 | void (*dev_print_pvt)(struct dev *dev); 191 | 192 | void (*prologue)(void); 193 | void (*join)(void); 194 | void (*epilogue)(void); 195 | }; 196 | 197 | static inline int env_print_ports(const struct printer *p, struct env *env) 198 | { 199 | if (!p->env_print_ports) 200 | return -ENOENT; 201 | 202 | p->env_print_ports(env); 203 | return 0; 204 | } 205 | 206 | static inline int env_print_atu(const struct printer *p, struct env *env) 207 | { 208 | if (!p->env_print_atu) 209 | return -ENOENT; 210 | 211 | p->env_print_atu(env); 212 | return 0; 213 | } 214 | 215 | static inline int env_print_vtu(const struct printer *p, struct env *env) 216 | { 217 | if (!p->env_print_vtu) 218 | return -ENOENT; 219 | 220 | p->env_print_vtu(env); 221 | return 0; 222 | } 223 | 224 | static inline int env_print_stu(const struct printer *p, struct env *env) 225 | { 226 | if (!p->env_print_stu) 227 | return -ENOENT; 228 | 229 | p->env_print_stu(env); 230 | return 0; 231 | } 232 | 233 | static inline int env_print_pvt(const struct printer *p, struct env *env) 234 | { 235 | if (!p->env_print_pvt) 236 | return -ENOENT; 237 | 238 | p->env_print_pvt(env); 239 | return 0; 240 | } 241 | 242 | static inline int dev_print_pvt(const struct printer *p, struct dev *dev) 243 | { 244 | if (!p->dev_print_pvt) 245 | return -ENOENT; 246 | 247 | p->dev_print_pvt(dev); 248 | return 0; 249 | } 250 | 251 | static inline int print_prologue(const struct printer *p) 252 | { 253 | if (!p->prologue) 254 | return -ENOENT; 255 | 256 | p->prologue(); 257 | return 0; 258 | } 259 | 260 | static inline int print_join(const struct printer *p) 261 | { 262 | if (!p->join) 263 | return -ENOENT; 264 | 265 | p->join(); 266 | return 0; 267 | } 268 | 269 | static inline int print_epilogue(const struct printer *p) 270 | { 271 | if (!p->epilogue) 272 | return -ENOENT; 273 | 274 | p->epilogue(); 275 | return 0; 276 | } 277 | 278 | extern const struct printer printer_show; 279 | extern const struct printer printer_json; 280 | 281 | #endif /* __MVLS_H */ 282 | -------------------------------------------------------------------------------- /src/mvls/queue.h: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: queue.h,v 1.43 2015/12/28 19:38:40 millert Exp $ */ 2 | /* $NetBSD: queue.h,v 1.11 1996/05/16 05:17:14 mycroft Exp $ */ 3 | 4 | /* 5 | * Copyright (c) 1991, 1993 6 | * The Regents of the University of California. All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions 10 | * are met: 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 2. Redistributions in binary form must reproduce the above copyright 14 | * notice, this list of conditions and the following disclaimer in the 15 | * documentation and/or other materials provided with the distribution. 16 | * 3. Neither the name of the University nor the names of its contributors 17 | * may be used to endorse or promote products derived from this software 18 | * without specific prior written permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 21 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | * 32 | * @(#)queue.h 8.5 (Berkeley) 8/20/94 33 | */ 34 | 35 | #ifdef __cplusplus 36 | extern "C" 37 | { 38 | #endif 39 | 40 | #ifndef _SYS_QUEUE_H_ 41 | #define _SYS_QUEUE_H_ 42 | 43 | /** 44 | * @file queue.h 45 | * @author The Regents of the University of California 46 | * @date 1991, 1993 47 | * @copyright 3-clause BSD License 48 | * 49 | * This file defines five types of data structures: singly-linked lists, 50 | * lists, simple queues, tail queues and XOR simple queues. 51 | * 52 | * 53 | * A singly-linked list is headed by a single forward pointer. The elements 54 | * are singly linked for minimum space and pointer manipulation overhead at 55 | * the expense of O(n) removal for arbitrary elements. New elements can be 56 | * added to the list after an existing element or at the head of the list. 57 | * Elements being removed from the head of the list should use the explicit 58 | * macro for this purpose for optimum efficiency. A singly-linked list may 59 | * only be traversed in the forward direction. Singly-linked lists are ideal 60 | * for applications with large datasets and few or no removals or for 61 | * implementing a LIFO queue. 62 | * 63 | * A list is headed by a single forward pointer (or an array of forward 64 | * pointers for a hash table header). The elements are doubly linked 65 | * so that an arbitrary element can be removed without a need to 66 | * traverse the list. New elements can be added to the list before 67 | * or after an existing element or at the head of the list. A list 68 | * may only be traversed in the forward direction. 69 | * 70 | * A simple queue is headed by a pair of pointers, one to the head of the 71 | * list and the other to the tail of the list. The elements are singly 72 | * linked to save space, so elements can only be removed from the 73 | * head of the list. New elements can be added to the list before or after 74 | * an existing element, at the head of the list, or at the end of the 75 | * list. A simple queue may only be traversed in the forward direction. 76 | * 77 | * A tail queue is headed by a pair of pointers, one to the head of the 78 | * list and the other to the tail of the list. The elements are doubly 79 | * linked so that an arbitrary element can be removed without a need to 80 | * traverse the list. New elements can be added to the list before or 81 | * after an existing element, at the head of the list, or at the end of 82 | * the list. A tail queue may be traversed in either direction. 83 | * 84 | * An XOR simple queue is used in the same way as a regular simple queue. 85 | * The difference is that the head structure also includes a "cookie" that 86 | * is XOR'd with the queue pointer (first, last or next) to generate the 87 | * real pointer value. 88 | * 89 | * For details on the use of these macros, see the queue(3) manual page. 90 | */ 91 | 92 | #if defined(QUEUE_MACRO_DEBUG) || (defined(_KERNEL) && defined(DIAGNOSTIC)) 93 | #define _Q_INVALIDATE(a) (a) = ((void *)-1) 94 | #else 95 | #define _Q_INVALIDATE(a) 96 | #endif 97 | 98 | /* 99 | * Singly-linked List definitions. 100 | */ 101 | #define SLIST_HEAD(name, type) \ 102 | struct name { \ 103 | struct type *slh_first; /* first element */ \ 104 | } 105 | 106 | #define SLIST_HEAD_INITIALIZER(head) \ 107 | { NULL } 108 | 109 | #define SLIST_ENTRY(type) \ 110 | struct { \ 111 | struct type *sle_next; /* next element */ \ 112 | } 113 | 114 | /* 115 | * Singly-linked List access methods. 116 | */ 117 | #define SLIST_FIRST(head) ((head)->slh_first) 118 | #define SLIST_END(head) NULL 119 | #define SLIST_EMPTY(head) (SLIST_FIRST(head) == SLIST_END(head)) 120 | #define SLIST_NEXT(elm, field) ((elm)->field.sle_next) 121 | 122 | #define SLIST_FOREACH(var, head, field) \ 123 | for((var) = SLIST_FIRST(head); \ 124 | (var) != SLIST_END(head); \ 125 | (var) = SLIST_NEXT(var, field)) 126 | 127 | #define SLIST_FOREACH_SAFE(var, head, field, tvar) \ 128 | for ((var) = SLIST_FIRST(head); \ 129 | (var) && ((tvar) = SLIST_NEXT(var, field), 1); \ 130 | (var) = (tvar)) 131 | 132 | /* 133 | * Singly-linked List functions. 134 | */ 135 | #define SLIST_INIT(head) { \ 136 | SLIST_FIRST(head) = SLIST_END(head); \ 137 | } 138 | 139 | #define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ 140 | (elm)->field.sle_next = (slistelm)->field.sle_next; \ 141 | (slistelm)->field.sle_next = (elm); \ 142 | } while (0) 143 | 144 | #define SLIST_INSERT_HEAD(head, elm, field) do { \ 145 | (elm)->field.sle_next = (head)->slh_first; \ 146 | (head)->slh_first = (elm); \ 147 | } while (0) 148 | 149 | #define SLIST_REMOVE_AFTER(elm, field) do { \ 150 | (elm)->field.sle_next = (elm)->field.sle_next->field.sle_next; \ 151 | } while (0) 152 | 153 | #define SLIST_REMOVE_HEAD(head, field) do { \ 154 | (head)->slh_first = (head)->slh_first->field.sle_next; \ 155 | } while (0) 156 | 157 | #define SLIST_REMOVE(head, elm, type, field) do { \ 158 | if ((head)->slh_first == (elm)) { \ 159 | SLIST_REMOVE_HEAD((head), field); \ 160 | } else { \ 161 | struct type *curelm = (head)->slh_first; \ 162 | \ 163 | while (curelm->field.sle_next != (elm)) \ 164 | curelm = curelm->field.sle_next; \ 165 | curelm->field.sle_next = \ 166 | curelm->field.sle_next->field.sle_next; \ 167 | } \ 168 | _Q_INVALIDATE((elm)->field.sle_next); \ 169 | } while (0) 170 | 171 | /* 172 | * List definitions. 173 | */ 174 | #define LIST_HEAD(name, type) \ 175 | struct name { \ 176 | struct type *lh_first; /* first element */ \ 177 | } 178 | 179 | #define LIST_HEAD_INITIALIZER(head) \ 180 | { NULL } 181 | 182 | #define LIST_ENTRY(type) \ 183 | struct { \ 184 | struct type *le_next; /* next element */ \ 185 | struct type **le_prev; /* address of previous next element */ \ 186 | } 187 | 188 | /* 189 | * List access methods. 190 | */ 191 | #define LIST_FIRST(head) ((head)->lh_first) 192 | #define LIST_END(head) NULL 193 | #define LIST_EMPTY(head) (LIST_FIRST(head) == LIST_END(head)) 194 | #define LIST_NEXT(elm, field) ((elm)->field.le_next) 195 | 196 | #define LIST_FOREACH(var, head, field) \ 197 | for((var) = LIST_FIRST(head); \ 198 | (var)!= LIST_END(head); \ 199 | (var) = LIST_NEXT(var, field)) 200 | 201 | #define LIST_FOREACH_SAFE(var, head, field, tvar) \ 202 | for ((var) = LIST_FIRST(head); \ 203 | (var) && ((tvar) = LIST_NEXT(var, field), 1); \ 204 | (var) = (tvar)) 205 | 206 | /* 207 | * List functions. 208 | */ 209 | #define LIST_INIT(head) do { \ 210 | LIST_FIRST(head) = LIST_END(head); \ 211 | } while (0) 212 | 213 | #define LIST_INSERT_AFTER(listelm, elm, field) do { \ 214 | if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \ 215 | (listelm)->field.le_next->field.le_prev = \ 216 | &(elm)->field.le_next; \ 217 | (listelm)->field.le_next = (elm); \ 218 | (elm)->field.le_prev = &(listelm)->field.le_next; \ 219 | } while (0) 220 | 221 | #define LIST_INSERT_BEFORE(listelm, elm, field) do { \ 222 | (elm)->field.le_prev = (listelm)->field.le_prev; \ 223 | (elm)->field.le_next = (listelm); \ 224 | *(listelm)->field.le_prev = (elm); \ 225 | (listelm)->field.le_prev = &(elm)->field.le_next; \ 226 | } while (0) 227 | 228 | #define LIST_INSERT_HEAD(head, elm, field) do { \ 229 | if (((elm)->field.le_next = (head)->lh_first) != NULL) \ 230 | (head)->lh_first->field.le_prev = &(elm)->field.le_next;\ 231 | (head)->lh_first = (elm); \ 232 | (elm)->field.le_prev = &(head)->lh_first; \ 233 | } while (0) 234 | 235 | #define LIST_REMOVE(elm, field) do { \ 236 | if ((elm)->field.le_next != NULL) \ 237 | (elm)->field.le_next->field.le_prev = \ 238 | (elm)->field.le_prev; \ 239 | *(elm)->field.le_prev = (elm)->field.le_next; \ 240 | _Q_INVALIDATE((elm)->field.le_prev); \ 241 | _Q_INVALIDATE((elm)->field.le_next); \ 242 | } while (0) 243 | 244 | #define LIST_REPLACE(elm, elm2, field) do { \ 245 | if (((elm2)->field.le_next = (elm)->field.le_next) != NULL) \ 246 | (elm2)->field.le_next->field.le_prev = \ 247 | &(elm2)->field.le_next; \ 248 | (elm2)->field.le_prev = (elm)->field.le_prev; \ 249 | *(elm2)->field.le_prev = (elm2); \ 250 | _Q_INVALIDATE((elm)->field.le_prev); \ 251 | _Q_INVALIDATE((elm)->field.le_next); \ 252 | } while (0) 253 | 254 | /* 255 | * Simple queue definitions. 256 | */ 257 | #define SIMPLEQ_HEAD(name, type) \ 258 | struct name { \ 259 | struct type *sqh_first; /* first element */ \ 260 | struct type **sqh_last; /* addr of last next element */ \ 261 | } 262 | 263 | #define SIMPLEQ_HEAD_INITIALIZER(head) \ 264 | { NULL, &(head).sqh_first } 265 | 266 | #define SIMPLEQ_ENTRY(type) \ 267 | struct { \ 268 | struct type *sqe_next; /* next element */ \ 269 | } 270 | 271 | /* 272 | * Simple queue access methods. 273 | */ 274 | #define SIMPLEQ_FIRST(head) ((head)->sqh_first) 275 | #define SIMPLEQ_END(head) NULL 276 | #define SIMPLEQ_EMPTY(head) (SIMPLEQ_FIRST(head) == SIMPLEQ_END(head)) 277 | #define SIMPLEQ_NEXT(elm, field) ((elm)->field.sqe_next) 278 | 279 | #define SIMPLEQ_FOREACH(var, head, field) \ 280 | for((var) = SIMPLEQ_FIRST(head); \ 281 | (var) != SIMPLEQ_END(head); \ 282 | (var) = SIMPLEQ_NEXT(var, field)) 283 | 284 | #define SIMPLEQ_FOREACH_SAFE(var, head, field, tvar) \ 285 | for ((var) = SIMPLEQ_FIRST(head); \ 286 | (var) && ((tvar) = SIMPLEQ_NEXT(var, field), 1); \ 287 | (var) = (tvar)) 288 | 289 | /* 290 | * Simple queue functions. 291 | */ 292 | #define SIMPLEQ_INIT(head) do { \ 293 | (head)->sqh_first = NULL; \ 294 | (head)->sqh_last = &(head)->sqh_first; \ 295 | } while (0) 296 | 297 | #define SIMPLEQ_INSERT_HEAD(head, elm, field) do { \ 298 | if (((elm)->field.sqe_next = (head)->sqh_first) == NULL) \ 299 | (head)->sqh_last = &(elm)->field.sqe_next; \ 300 | (head)->sqh_first = (elm); \ 301 | } while (0) 302 | 303 | #define SIMPLEQ_INSERT_TAIL(head, elm, field) do { \ 304 | (elm)->field.sqe_next = NULL; \ 305 | *(head)->sqh_last = (elm); \ 306 | (head)->sqh_last = &(elm)->field.sqe_next; \ 307 | } while (0) 308 | 309 | #define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ 310 | if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\ 311 | (head)->sqh_last = &(elm)->field.sqe_next; \ 312 | (listelm)->field.sqe_next = (elm); \ 313 | } while (0) 314 | 315 | #define SIMPLEQ_REMOVE_HEAD(head, field) do { \ 316 | if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \ 317 | (head)->sqh_last = &(head)->sqh_first; \ 318 | } while (0) 319 | 320 | #define SIMPLEQ_REMOVE_AFTER(head, elm, field) do { \ 321 | if (((elm)->field.sqe_next = (elm)->field.sqe_next->field.sqe_next) \ 322 | == NULL) \ 323 | (head)->sqh_last = &(elm)->field.sqe_next; \ 324 | } while (0) 325 | 326 | #define SIMPLEQ_CONCAT(head1, head2) do { \ 327 | if (!SIMPLEQ_EMPTY((head2))) { \ 328 | *(head1)->sqh_last = (head2)->sqh_first; \ 329 | (head1)->sqh_last = (head2)->sqh_last; \ 330 | SIMPLEQ_INIT((head2)); \ 331 | } \ 332 | } while (0) 333 | 334 | /* 335 | * XOR Simple queue definitions. 336 | */ 337 | #define XSIMPLEQ_HEAD(name, type) \ 338 | struct name { \ 339 | struct type *sqx_first; /* first element */ \ 340 | struct type **sqx_last; /* addr of last next element */ \ 341 | unsigned long sqx_cookie; \ 342 | } 343 | 344 | #define XSIMPLEQ_ENTRY(type) \ 345 | struct { \ 346 | struct type *sqx_next; /* next element */ \ 347 | } 348 | 349 | /* 350 | * XOR Simple queue access methods. 351 | */ 352 | #define XSIMPLEQ_XOR(head, ptr) ((__typeof(ptr))((head)->sqx_cookie ^ \ 353 | (unsigned long)(ptr))) 354 | #define XSIMPLEQ_FIRST(head) XSIMPLEQ_XOR(head, ((head)->sqx_first)) 355 | #define XSIMPLEQ_END(head) NULL 356 | #define XSIMPLEQ_EMPTY(head) (XSIMPLEQ_FIRST(head) == XSIMPLEQ_END(head)) 357 | #define XSIMPLEQ_NEXT(head, elm, field) XSIMPLEQ_XOR(head, ((elm)->field.sqx_next)) 358 | 359 | 360 | #define XSIMPLEQ_FOREACH(var, head, field) \ 361 | for ((var) = XSIMPLEQ_FIRST(head); \ 362 | (var) != XSIMPLEQ_END(head); \ 363 | (var) = XSIMPLEQ_NEXT(head, var, field)) 364 | 365 | #define XSIMPLEQ_FOREACH_SAFE(var, head, field, tvar) \ 366 | for ((var) = XSIMPLEQ_FIRST(head); \ 367 | (var) && ((tvar) = XSIMPLEQ_NEXT(head, var, field), 1); \ 368 | (var) = (tvar)) 369 | 370 | /* 371 | * XOR Simple queue functions. 372 | */ 373 | #define XSIMPLEQ_INIT(head) do { \ 374 | arc4random_buf(&(head)->sqx_cookie, sizeof((head)->sqx_cookie)); \ 375 | (head)->sqx_first = XSIMPLEQ_XOR(head, NULL); \ 376 | (head)->sqx_last = XSIMPLEQ_XOR(head, &(head)->sqx_first); \ 377 | } while (0) 378 | 379 | #define XSIMPLEQ_INSERT_HEAD(head, elm, field) do { \ 380 | if (((elm)->field.sqx_next = (head)->sqx_first) == \ 381 | XSIMPLEQ_XOR(head, NULL)) \ 382 | (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \ 383 | (head)->sqx_first = XSIMPLEQ_XOR(head, (elm)); \ 384 | } while (0) 385 | 386 | #define XSIMPLEQ_INSERT_TAIL(head, elm, field) do { \ 387 | (elm)->field.sqx_next = XSIMPLEQ_XOR(head, NULL); \ 388 | *(XSIMPLEQ_XOR(head, (head)->sqx_last)) = XSIMPLEQ_XOR(head, (elm)); \ 389 | (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \ 390 | } while (0) 391 | 392 | #define XSIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ 393 | if (((elm)->field.sqx_next = (listelm)->field.sqx_next) == \ 394 | XSIMPLEQ_XOR(head, NULL)) \ 395 | (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \ 396 | (listelm)->field.sqx_next = XSIMPLEQ_XOR(head, (elm)); \ 397 | } while (0) 398 | 399 | #define XSIMPLEQ_REMOVE_HEAD(head, field) do { \ 400 | if (((head)->sqx_first = XSIMPLEQ_XOR(head, \ 401 | (head)->sqx_first)->field.sqx_next) == XSIMPLEQ_XOR(head, NULL)) \ 402 | (head)->sqx_last = XSIMPLEQ_XOR(head, &(head)->sqx_first); \ 403 | } while (0) 404 | 405 | #define XSIMPLEQ_REMOVE_AFTER(head, elm, field) do { \ 406 | if (((elm)->field.sqx_next = XSIMPLEQ_XOR(head, \ 407 | (elm)->field.sqx_next)->field.sqx_next) \ 408 | == XSIMPLEQ_XOR(head, NULL)) \ 409 | (head)->sqx_last = \ 410 | XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \ 411 | } while (0) 412 | 413 | 414 | /* 415 | * Tail queue definitions. 416 | */ 417 | #define TAILQ_HEAD(name, type) \ 418 | struct name { \ 419 | struct type *tqh_first; /* first element */ \ 420 | struct type **tqh_last; /* addr of last next element */ \ 421 | } 422 | 423 | #define TAILQ_HEAD_INITIALIZER(head) \ 424 | { NULL, &(head).tqh_first } 425 | 426 | #define TAILQ_ENTRY(type) \ 427 | struct { \ 428 | struct type *tqe_next; /* next element */ \ 429 | struct type **tqe_prev; /* address of previous next element */ \ 430 | } 431 | 432 | /* 433 | * Tail queue access methods. 434 | */ 435 | #define TAILQ_FIRST(head) ((head)->tqh_first) 436 | #define TAILQ_END(head) NULL 437 | #define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) 438 | #define TAILQ_LAST(head, headname) \ 439 | (*(((struct headname *)((head)->tqh_last))->tqh_last)) 440 | /* XXX */ 441 | #define TAILQ_PREV(elm, headname, field) \ 442 | (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) 443 | #define TAILQ_EMPTY(head) \ 444 | (TAILQ_FIRST(head) == TAILQ_END(head)) 445 | 446 | #define TAILQ_FOREACH(var, head, field) \ 447 | for((var) = TAILQ_FIRST(head); \ 448 | (var) != TAILQ_END(head); \ 449 | (var) = TAILQ_NEXT(var, field)) 450 | 451 | #define TAILQ_FOREACH_SAFE(var, head, field, tvar) \ 452 | for ((var) = TAILQ_FIRST(head); \ 453 | (var) != TAILQ_END(head) && \ 454 | ((tvar) = TAILQ_NEXT(var, field), 1); \ 455 | (var) = (tvar)) 456 | 457 | 458 | #define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ 459 | for((var) = TAILQ_LAST(head, headname); \ 460 | (var) != TAILQ_END(head); \ 461 | (var) = TAILQ_PREV(var, headname, field)) 462 | 463 | #define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \ 464 | for ((var) = TAILQ_LAST(head, headname); \ 465 | (var) != TAILQ_END(head) && \ 466 | ((tvar) = TAILQ_PREV(var, headname, field), 1); \ 467 | (var) = (tvar)) 468 | 469 | /* 470 | * Tail queue functions. 471 | */ 472 | #define TAILQ_INIT(head) do { \ 473 | (head)->tqh_first = NULL; \ 474 | (head)->tqh_last = &(head)->tqh_first; \ 475 | } while (0) 476 | 477 | #define TAILQ_INSERT_HEAD(head, elm, field) do { \ 478 | if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \ 479 | (head)->tqh_first->field.tqe_prev = \ 480 | &(elm)->field.tqe_next; \ 481 | else \ 482 | (head)->tqh_last = &(elm)->field.tqe_next; \ 483 | (head)->tqh_first = (elm); \ 484 | (elm)->field.tqe_prev = &(head)->tqh_first; \ 485 | } while (0) 486 | 487 | #define TAILQ_INSERT_TAIL(head, elm, field) do { \ 488 | (elm)->field.tqe_next = NULL; \ 489 | (elm)->field.tqe_prev = (head)->tqh_last; \ 490 | *(head)->tqh_last = (elm); \ 491 | (head)->tqh_last = &(elm)->field.tqe_next; \ 492 | } while (0) 493 | 494 | #define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ 495 | if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\ 496 | (elm)->field.tqe_next->field.tqe_prev = \ 497 | &(elm)->field.tqe_next; \ 498 | else \ 499 | (head)->tqh_last = &(elm)->field.tqe_next; \ 500 | (listelm)->field.tqe_next = (elm); \ 501 | (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \ 502 | } while (0) 503 | 504 | #define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ 505 | (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ 506 | (elm)->field.tqe_next = (listelm); \ 507 | *(listelm)->field.tqe_prev = (elm); \ 508 | (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \ 509 | } while (0) 510 | 511 | #define TAILQ_REMOVE(head, elm, field) do { \ 512 | if (((elm)->field.tqe_next) != NULL) \ 513 | (elm)->field.tqe_next->field.tqe_prev = \ 514 | (elm)->field.tqe_prev; \ 515 | else \ 516 | (head)->tqh_last = (elm)->field.tqe_prev; \ 517 | *(elm)->field.tqe_prev = (elm)->field.tqe_next; \ 518 | _Q_INVALIDATE((elm)->field.tqe_prev); \ 519 | _Q_INVALIDATE((elm)->field.tqe_next); \ 520 | } while (0) 521 | 522 | #define TAILQ_REPLACE(head, elm, elm2, field) do { \ 523 | if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL) \ 524 | (elm2)->field.tqe_next->field.tqe_prev = \ 525 | &(elm2)->field.tqe_next; \ 526 | else \ 527 | (head)->tqh_last = &(elm2)->field.tqe_next; \ 528 | (elm2)->field.tqe_prev = (elm)->field.tqe_prev; \ 529 | *(elm2)->field.tqe_prev = (elm2); \ 530 | _Q_INVALIDATE((elm)->field.tqe_prev); \ 531 | _Q_INVALIDATE((elm)->field.tqe_next); \ 532 | } while (0) 533 | 534 | #define TAILQ_CONCAT(head1, head2, field) do { \ 535 | if (!TAILQ_EMPTY(head2)) { \ 536 | *(head1)->tqh_last = (head2)->tqh_first; \ 537 | (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \ 538 | (head1)->tqh_last = (head2)->tqh_last; \ 539 | TAILQ_INIT((head2)); \ 540 | } \ 541 | } while (0) 542 | 543 | #endif /* !_SYS_QUEUE_H_ */ 544 | 545 | #ifdef __cplusplus 546 | } 547 | #endif 548 | --------------------------------------------------------------------------------