pax_global_header00006660000000000000000000000064147673754450014540gustar00rootroot0000000000000052 comment=92d5203077553bfc9f7bf1c219563db0fc28e660 ndctl-81/000077500000000000000000000000001476737544500124365ustar00rootroot00000000000000ndctl-81/.clang-format000066400000000000000000000112561476737544500150160ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-2.0 # # clang-format configuration file. Intended for clang-format >= 11. # Copied from Linux's .clang-format # # For more information, see: # # https://clang.llvm.org/docs/ClangFormat.html # https://clang.llvm.org/docs/ClangFormatStyleOptions.html # --- AccessModifierOffset: -4 AlignAfterOpenBracket: Align AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignEscapedNewlines: Left AlignConsecutiveMacros: true AlignOperands: true AlignTrailingComments: false AllowAllParametersOfDeclarationOnNextLine: false AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: None AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: false BinPackArguments: true BinPackParameters: true BraceWrapping: AfterClass: false AfterControlStatement: false AfterEnum: false AfterFunction: true AfterNamespace: true AfterObjCDeclaration: false AfterStruct: false AfterUnion: false AfterExternBlock: false BeforeCatch: false BeforeElse: false IndentBraces: false SplitEmptyFunction: true SplitEmptyRecord: true SplitEmptyNamespace: true BreakBeforeBinaryOperators: None BreakBeforeBraces: Custom BreakBeforeInheritanceComma: false BreakBeforeTernaryOperators: false BreakConstructorInitializersBeforeComma: false BreakConstructorInitializers: BeforeComma BreakAfterJavaFieldAnnotations: false BreakStringLiterals: false ColumnLimit: 80 CommentPragmas: '^ IWYU pragma:' CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: false ConstructorInitializerIndentWidth: 8 ContinuationIndentWidth: 8 Cpp11BracedListStyle: false DerivePointerAlignment: false DisableFormat: false ExperimentalAutoDetectBinPacking: false FixNamespaceComments: false # Taken from: # while read -r sym; do # printf " - '%s'\n" "$sym"; # done < \ # <(cscope -dL6 "foreach|for_each" \ # | awk '{ print $4 $5 }' | grep -E 'foreach|for_each' \ # | sed -e 's/#define//' \ # -e 's/*//' \ # -e 's/://' \ # -e 's/\(.*for_each.*\)(.*/\1/' \ # -e 's/\(.*foreach.*\)(.*/\1/' \ # | sort -u) ForEachMacros: - 'cxl_memdev_foreach' - 'cxl_bus_foreach' - 'cxl_port_foreach' - 'cxl_decoder_foreach' - 'cxl_decoder_foreach_reverse' - 'cxl_target_foreach' - 'cxl_dport_foreach' - 'cxl_endpoint_foreach' - 'cxl_port_foreach_all' - 'cxl_region_foreach' - 'cxl_region_foreach_safe' - 'daxctl_dev_foreach' - 'daxctl_mapping_foreach' - 'daxctl_region_foreach' - 'kmod_list_foreach' - 'kmod_list_foreach_reverse' - 'list_for_each' - 'list_for_each_off' - 'list_for_each_rev' - 'list_for_each_safe' - 'list_for_each_safe_off' - 'ndctl_btt_foreach' - 'ndctl_btt_foreach_safe' - 'ndctl_bus_foreach' - 'ndctl_dax_foreach' - 'ndctl_dax_foreach_safe' - 'ndctl_dimm_foreach' - 'ndctl_dimm_foreach_in_interleave_set' - 'ndctl_dimm_foreach_in_region' - 'ndctl_interleave_set_foreach' - 'ndctl_mapping_foreach' - 'ndctl_namespace_badblock_foreach' - 'ndctl_namespace_bb_foreach' - 'ndctl_namespace_foreach' - 'ndctl_namespace_foreach_safe' - 'ndctl_pfn_foreach' - 'ndctl_pfn_foreach_safe' - 'ndctl_region_badblock_foreach' - 'ndctl_region_foreach' - 'udev_list_entry_foreach' IncludeBlocks: Preserve IncludeCategories: - Regex: '.*' Priority: 1 IncludeIsMainRegex: '(Test)?$' IndentCaseLabels: false IndentPPDirectives: None IndentWidth: 8 IndentWrappedFunctionNames: false JavaScriptQuotes: Leave JavaScriptWrapImports: true KeepEmptyLinesAtTheStartOfBlocks: false MacroBlockBegin: '' MacroBlockEnd: '' MaxEmptyLinesToKeep: 1 NamespaceIndentation: None ObjCBinPackProtocolList: Auto ObjCBlockIndentWidth: 8 ObjCSpaceAfterProperty: true ObjCSpaceBeforeProtocolList: true # Taken from git's rules PenaltyBreakAssignment: 10 PenaltyBreakBeforeFirstCallParameter: 30 PenaltyBreakComment: 10 PenaltyBreakFirstLessLess: 0 PenaltyBreakString: 10 PenaltyExcessCharacter: 100 PenaltyReturnTypeOnItsOwnLine: 60 PointerAlignment: Right ReflowComments: false SortIncludes: false SortUsingDeclarations: false SpaceAfterCStyleCast: false SpaceAfterTemplateKeyword: true SpaceBeforeAssignmentOperators: true SpaceBeforeCtorInitializerColon: true SpaceBeforeInheritanceColon: true SpaceBeforeParens: ControlStatementsExceptForEachMacros SpaceBeforeRangeBasedForLoopColon: true SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: false SpacesInContainerLiterals: false SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false Standard: Cpp03 TabWidth: 8 UseTab: Always ... ndctl-81/.gitignore000066400000000000000000000001411476737544500144220ustar00rootroot00000000000000build/ release/ rhel/ndctl.spec sles/ndctl.spec *.swp tags cscope.* scripts/docsurgeon_parser.sh ndctl-81/CONTRIBUTING.md000066400000000000000000000051621476737544500146730ustar00rootroot00000000000000# Contributing to ndctl Thank you for taking the time to contribute to ndctl. The following is a set of guidelines that we adhere to, and request that contributors follow. 1. **NOTE**: Since cxl-cli and libcxl are part of the ndctl project, any changes or questions relevant to CXL should also CC the CXL mailing list **```linux-cxl@vger.kernel.org```**. 1. The libnvdimm (kernel subsystem) and ndctl developers primarily use the [nvdimm](https://subspace.kernel.org/lists.linux.dev.html) mailing list for everything. It is recommended to send patches to **```nvdimm@lists.linux.dev```** and CC **```linux-cxl@vger.kernel.org```** if needed. The archives are available on [nvdimm](https://lore.kernel.org/nvdimm/) and [cxl](https://lore.kernel.org/linux-cxl/) 1. Github [issues](https://github.com/pmem/ndctl/issues) are an acceptable way to report a problem, but if you just have a question, [email](mailto:nvdimm@lists.linux.dev) the above list and CC `linux-cxl@linux-cxl@vger.kernel.org` if needed. 1. We follow the Linux Kernel [Coding Style Guide][cs] as applicable. [cs]: https://www.kernel.org/doc/html/latest/process/coding-style.html 1. We follow the Linux Kernel [Submitting Patches Guide][sp] as applicable. [sp]: https://www.kernel.org/doc/html/latest/process/submitting-patches.html 1. We follow the Linux Kernel [DCO][dco] (Developer Certificate of Origin). The DCO is an attestation attached to every contribution made by every developer. In the commit message of the contribution, the developer simply adds a Signed-off-by statement and thereby agrees to the DCO. [dco]: https://developercertificate.org/ 1. Github Pull Requests are acceptable mainly for smaller, more obvious fixups, but won't be merged directly, as Github doesn't allow for the kernel style flow of patches where a maintainer also signs off on the patches they apply. Larger changes may need to be sent to the mailing list so that everyone gets an opportunity to review them. 1. **Misc Best Practices:** 1. Use a subject prefix of "ndctl PATCH" (or "ndctl PATCH vN" for a new revision). This can be automated for a ```git format-patch``` command by setting a repo-local git config setting: ```git config format.subjectprefix "ndctl PATCH"``` 1. For commit messages: Describe the change and why it was needed. Use a concise subject line, and a blank line between the subject and the body, as well as between paragraphs. Use present tense and the imperative mood (e.g. "Add support for.." instead of "Added support.." or "Adding support"). Word-wrap to 72 columns. ndctl-81/COPYING000066400000000000000000000011701476737544500134700ustar00rootroot00000000000000The ndctl project provides tools under: SPDX-License-Identifier: GPL-2.0 Being under the terms of the GNU General Public License version 2 only, according with: LICENSES/preferred/GPL-2.0 The ndctl project provides libraries under: SPDX-License-Identifier: LGPL-2.1 Being under the terms of the GNU Lesser General Public License version 2.1 only, according with: LICENSES/preferred/LGPL-2.1 The project incorporates helper routines from the CCAN project under CC0-1.0 and MIT licenses according with: LICENSES/other/CC0-1.0 LICENSES/other/MIT All contributions to the ndctl project are subject to this COPYING file. ndctl-81/Documentation/000077500000000000000000000000001476737544500152475ustar00rootroot00000000000000ndctl-81/Documentation/COPYING000066400000000000000000000433341476737544500163110ustar00rootroot00000000000000All files in this directory (Documentation/) unless otherwise noted in the file itself are licensed under the GPLv2. --------- GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. ndctl-81/Documentation/asciidoc.conf.in000066400000000000000000000046551476737544500203130ustar00rootroot00000000000000## linkUTILITY: macro # # Copyright (c) 2005, Sergey Vlasov # Copyright (c) 2005, Jonas Fonseca # # Originally copied from GIT source (commit d1c2e113c5b6 "[PATCH] # Documentation: Add asciidoc.conf file and gitlink: macro") # # Usage: linkUTILITY:command[manpage-section] # # Note, {0} is the manpage section, while {target} is the command. # # Show PERF link as: (
); if section is defined, else just show # the command. [macros] (?su)[\\]?(?PlinkUTILITY):(?P\S*?)\[(?P.*?)\]= [attributes] asterisk=* plus=+ caret=^ startsb=[ endsb=] tilde=~ ifdef::backend-docbook[] [linkUTILITY-inlinemacro] {0%{target}} {0#} {0#{target}{0}} {0#} endif::backend-docbook[] ifdef::backend-docbook[] ifndef::UTILITY-asciidoc-no-roff[] # "unbreak" docbook-xsl v1.68 for manpages. v1.69 works with or without this. # v1.72 breaks with this because it replaces dots not in roff requests. [listingblock] {title} ifdef::doctype-manpage[] .ft C endif::doctype-manpage[] | ifdef::doctype-manpage[] .ft endif::doctype-manpage[] {title#} endif::UTILITY-asciidoc-no-roff[] ifdef::UTILITY-asciidoc-no-roff[] ifdef::doctype-manpage[] # The following two small workarounds insert a simple paragraph after screen [listingblock] {title} | {title#} [verseblock] {title} {title%} {title#} | {title#} {title%} endif::doctype-manpage[] endif::UTILITY-asciidoc-no-roff[] endif::backend-docbook[] ifdef::doctype-manpage[] ifdef::backend-docbook[] [header] template::[header-declarations] {mantitle} {manvolnum} UTILITY {UTILITY_version} UTILITY Manual {manname} {manpurpose} endif::backend-docbook[] endif::doctype-manpage[] ifdef::backend-xhtml11[] [linkUTILITY-inlinemacro] {target}{0?({0})} endif::backend-xhtml11[] ndctl-81/Documentation/asciidoctor-extensions.rb.in000066400000000000000000000016341476737544500227050ustar00rootroot00000000000000require 'asciidoctor' require 'asciidoctor/extensions' module @Utility@ module Documentation class Link@Utility@Processor < Asciidoctor::Extensions::InlineMacroProcessor use_dsl named :chrome def process(parent, target, attrs) if parent.document.basebackend? 'html' prefix = parent.document.attr('@utility@-relative-html-prefix') %(#{target}(#{attrs[1]})\n) elsif parent.document.basebackend? 'manpage' "#{target}(#{attrs[1]})" elsif parent.document.basebackend? 'docbook' "\n" \ "#{target}" \ "#{attrs[1]}\n" \ "\n" end end end end end Asciidoctor::Extensions.register do inline_macro @Utility@::Documentation::Link@Utility@Processor, :link@utility@ end ndctl-81/Documentation/copyright.txt000066400000000000000000000004471476737544500200250ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 COPYRIGHT --------- Copyright (C) 2016 - 2022, Intel Corporation. License GPLv2: GNU GPL version 2 . This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. ndctl-81/Documentation/cxl/000077500000000000000000000000001476737544500160355ustar00rootroot00000000000000ndctl-81/Documentation/cxl/bus-option.txt000066400000000000000000000001411476737544500206710ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 -b:: --bus=:: Restrict the operation to the specified bus. ndctl-81/Documentation/cxl/cxl-create-region.txt000066400000000000000000000070741476737544500221160ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 cxl-create-region(1) ==================== NAME ---- cxl-create-region - Assemble a CXL region by setting up attributes of its constituent CXL memdevs. SYNOPSIS -------- [verse] 'cxl create-region []' include::region-description.txt[] For create-region, a size can optionally be specified, but if not, the maximum possible size for each memdev will be used up to the available decode capacity in the system for the given memory type. For persistent regions a UUID can optionally be specified, but if not, one will be generated. If the region-creation operation is successful, a region object will be emitted on stdout in JSON format (see examples). If the specified arguments cannot be satisfied with a legal configuration, then an appropriate error will be emitted on stderr. EXAMPLE ------- ---- #cxl create-region -m -d decoder0.1 -w 2 -g 1024 mem0 mem1 { "region":"region0", "resource":"0xc90000000", "size":"512.00 MiB (536.87 MB)", "interleave_ways":2, "interleave_granularity":1024, "mappings":[ { "position":1, "decoder":"decoder4.0" }, { "position":0, "decoder":"decoder3.0" } ] } created 1 region ---- OPTIONS ------- :: The CXL targets that should be used to form the region. The number of 'target' arguments must match the '--ways' option (if provided). include::bus-option.txt[] -m:: --memdevs:: Indicate that the non-option arguments for 'target(s)' refer to memdev device names. If this option is omitted and no targets are specified then create-region uses the equivalent of 'cxl list -M -d $decoder' internally as the target list. Note that depending on the topology, for example with switches, the automatic target list ordering may not be valid and manual specification of the target list is required. -s:: --size=:: Specify the total size for the new region. This is optional, and by default, the maximum possible size will be used. The maximum possible size is gated by both the contiguous free HPA space remaining in the root decoder, and the available DPA space in the component memdevs. -t:: --type=:: Specify the region type - 'pmem' or 'ram'. Default to root decoder capability, and if that is ambiguous, default to 'pmem'. -U:: --uuid=:: Specify a UUID for the new region. This shouldn't usually need to be specified, as one will be generated by default. Only applicable to pmem regions. -w:: --ways=:: The number of interleave ways for the new region's interleave. This should be equal to the number of memdevs specified in --memdevs, if --memdevs is being supplied. If --ways is not specified, it will be determined based on the number of memdev targets provided. -g:: --granularity=:: The interleave granularity for the new region. Must match the selected root decoder's (if provided) granularity. If the root decoder is interleaved across more than one host-bridge then this value must match that granularity. Otherwise, for non-interleaved decode windows, any granularity can be specified as long as all devices support that setting. -d:: --decoder=:: The root decoder that the region should be created under. If not supplied, the first cross-host bridge (if available), decoder that supports the largest interleave will be chosen. -Q:: --enforce-qos:: Parameter to enforce qos_class mismatch failure. Region create operation will fail of the qos_class of the root decoder and one of the memdev that backs the region mismatches. include::human-option.txt[] include::debug-option.txt[] include::../copyright.txt[] SEE ALSO -------- linkcxl:cxl-list[1], ndctl-81/Documentation/cxl/cxl-destroy-region.txt000066400000000000000000000012001476737544500223250ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 cxl-destroy-region(1) ===================== NAME ---- cxl-destroy-region - destroy specified region(s). SYNOPSIS -------- [verse] 'cxl destroy-region []' include::region-description.txt[] EXAMPLE ------- ---- # cxl destroy-region all destroyed 2 regions ---- OPTIONS ------- include::bus-option.txt[] -f:: --force:: Force a destroy operation even if the region is active. This will attempt to disable the region first. include::decoder-option.txt[] include::debug-option.txt[] include::../copyright.txt[] SEE ALSO -------- linkcxl:cxl-list[1], linkcxl:cxl-create-region[1] ndctl-81/Documentation/cxl/cxl-disable-bus.txt000066400000000000000000000015441476737544500215600ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 cxl-disable-bus(1) =================== NAME ---- cxl-disable-bus - Shutdown an entire tree of CXL devices SYNOPSIS -------- [verse] 'cxl disable-bus' [..] [] For test and debug scenarios, disable a CXL bus and any associated memory devices from CXL.mem operations. include::xable-no-op.txt[] OPTIONS ------- -f:: --force:: DANGEROUS: Override the safety measure that blocks attempts to disable a bus if the tool determines a descendent memdev is in active usage. Recall that CXL memory ranges might have been established by platform firmware and disabling an active device is akin to force removing memory from a running system. --debug:: If the cxl tool was built with debug disabled, turn on debug messages. include::../copyright.txt[] SEE ALSO -------- linkcxl:cxl-disable-port[1] ndctl-81/Documentation/cxl/cxl-disable-memdev.txt000066400000000000000000000020311476737544500222340ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 cxl-disable-memdev(1) ===================== NAME ---- cxl-disable-memdev - deactivate / hot-remove a given CXL memdev SYNOPSIS -------- [verse] 'cxl disable-memdev' [..] [] include::xable-no-op.txt[] OPTIONS ------- :: include::memdev-option.txt[] include::bus-option.txt[] -f:: --force:: DANGEROUS: Override the safety measure that blocks attempts to disable a device if the tool determines the memdev is in active usage. Recall that CXL memory ranges might have been established by platform firmware and disabling an active device is akin to force removing memory from a running system. Going down this path does not offline active memory if they are currently online. User is recommended to offline and disable the appropriate regions before disabling the memdevs. -v:: Turn on verbose debug messages in the library (if libcxl was built with logging and debug enabled). include::../copyright.txt[] SEE ALSO -------- linkcxl:cxl-enable-memdev[1] ndctl-81/Documentation/cxl/cxl-disable-port.txt000066400000000000000000000017151476737544500217530ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 cxl-disable-port(1) =================== NAME ---- cxl-disable-port - disable / hot-remove a given CXL port and descendants SYNOPSIS -------- [verse] 'cxl disable-port' [..] [] For test and debug scenarios, disable a CXL port and any memory devices dependent on this port being active for CXL.mem operation. include::xable-no-op.txt[] OPTIONS ------- -e:: --endpoint:: Toggle from treating the port arguments as Switch Port identifiers to Endpoint Port identifiers. -f:: --force:: DANGEROUS: Override the safety measure that blocks attempts to disable a port if the tool determines a descendent memdev is in active usage. Recall that CXL memory ranges might have been established by platform firmware and disabling an active device is akin to force removing memory from a running system. include::debug-option.txt[] include::../copyright.txt[] SEE ALSO -------- linkcxl:cxl-disable-port[1] ndctl-81/Documentation/cxl/cxl-disable-region.txt000066400000000000000000000016741476737544500222560ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 cxl-disable-region(1) ===================== NAME ---- cxl-disable-region - disable specified region(s). SYNOPSIS -------- [verse] 'cxl disable-region []' include::region-description.txt[] If there are memory blocks that are still online, the operation will attempt to offline the relevant blocks. If the offlining fails, the operation fails when not using the -f (force) parameter. EXAMPLE ------- ---- # cxl disable-region all disabled 2 regions ---- include::xable-no-op.txt[] OPTIONS ------- include::bus-option.txt[] -f:: --force:: Attempt to disable-region even though memory cannot be offlined successfully. Will emit warning that operation will permanently leak physical address space and cannot be recovered until a reboot. include::decoder-option.txt[] include::debug-option.txt[] include::../copyright.txt[] SEE ALSO -------- linkcxl:cxl-list[1], linkcxl:cxl-enable-region[1] ndctl-81/Documentation/cxl/cxl-enable-memdev.txt000066400000000000000000000015321476737544500220640ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 cxl-enable-memdev(1) ==================== NAME ---- cxl-enable-memdev - activate / hot-add a given CXL memdev SYNOPSIS -------- [verse] 'cxl enable-memdev' [..] [] A memdev typically autoenables at initial device discovery. However, if it was manually disabled this command can trigger the kernel to activate it again. This involves detecting the state of the HDM (Host Managed Device Memory) Decoders and validating that CXL.mem is enabled for each port in the device's hierarchy. include::xable-no-op.txt[] OPTIONS ------- :: include::memdev-option.txt[] include::bus-option.txt[] -v:: Turn on verbose debug messages in the library (if libcxl was built with logging and debug enabled). include::../copyright.txt[] SEE ALSO -------- linkcxl:cxl-disable-memdev[1] ndctl-81/Documentation/cxl/cxl-enable-port.txt000066400000000000000000000017441476737544500216000ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 cxl-enable-port(1) ================== NAME ---- cxl-enable-port - activate / hot-add a given CXL port SYNOPSIS -------- [verse] 'cxl enable-port' [..] [] A port typically autoenables at initial device discovery. However, if it was manually disabled this command can trigger the kernel to activate it again. This involves detecting the state of the HDM (Host Managed Device Memory) Decoders and validating that CXL.mem is enabled for each port in the device's hierarchy. include::xable-no-op.txt[] OPTIONS ------- -e:: --endpoint:: Toggle from treating the port arguments as Switch Port identifiers to Endpoint Port identifiers. -m:: --enable-memdevs:: Try to enable descendant memdevs after enabling the port. Recall that a memdev is only enabled after all CXL ports in its device topology ancestry are enabled. include::debug-option.txt[] include::../copyright.txt[] SEE ALSO -------- linkcxl:cxl-disable-port[1] ndctl-81/Documentation/cxl/cxl-enable-region.txt000066400000000000000000000010351476737544500220700ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 cxl-enable-region(1) ===================== NAME ---- cxl-enable-region - enable specified region(s). SYNOPSIS -------- [verse] 'cxl enable-region []' include::region-description.txt[] EXAMPLE ------- ---- # cxl enable-region all enabled 2 regions ---- include::xable-no-op.txt[] OPTIONS ------- include::bus-option.txt[] include::decoder-option.txt[] include::debug-option.txt[] include::../copyright.txt[] SEE ALSO -------- linkcxl:cxl-list[1], linkcxl:cxl-disable-region[1] ndctl-81/Documentation/cxl/cxl-free-dpa.txt000066400000000000000000000026721476737544500210540ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 cxl-free-dpa(1) =============== NAME ---- cxl-free-dpa - release device-physical address space SYNOPSIS -------- [verse] 'cxl free-dpa' [..] [] The CXL region provisioning process proceeds in multiple steps. One of the steps is identifying and reserving the DPA span that each member of the interleave-set (region) contributes in advance of attaching that allocation to a region. For development, test, and debug purposes this command is a helper to find the last allocated decoder on a device and zero-out / free its DPA allocation. OPTIONS ------- :: include::memdev-option.txt[] include::bus-option.txt[] -d:: --decoder:: Specify the decoder to free. The CXL specification mandates that DPA must be released in the reverse order it was allocated. See linkcxl:cxl-reserve-dpa[1] -t:: --type:: Constrain the search for "last allocated decoder" to decoders targeting the given partition. -f:: --force:: The kernel enforces CXL DPA ordering constraints on deallocation events, and the tool anticipates those and fails operations that are expected to fail without sending them to the kernel. For test purposes, continue to attempt "expected to fail" operations to exercise the driver. -v:: Turn on verbose debug messages in the library (if libcxl was built with logging and debug enabled). include::../copyright.txt[] SEE ALSO -------- linkcxl:cxl-reserve-dpa[1] ndctl-81/Documentation/cxl/cxl-list.txt000066400000000000000000000307421476737544500203430ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 cxl-list(1) =========== NAME ---- cxl-list - List platform CXL objects, and their attributes, in json. SYNOPSIS -------- [verse] 'cxl list' [] Walk the CXL capable device hierarchy in the system and list all device instances along with some of their major attributes. Options can be specified to limit the output to specific objects. When a single object type is specified the return json object is an array of just those objects, when multiple objects types are specified the returned object may be an array of arrays with the inner array named for the given object type. The top-level arrays are elided when the objects can nest under a higher object-type in the hierarchy. The potential top-level array names and their nesting properties are: "anon memdevs":: (disabled memory devices) do not nest "buses":: do not nest "ports":: nest under buses "endpoints":: nest under ports or buses (if ports are not emitted) "memdevs":: nest under endpoints or ports (if endpoints are not emitted) or buses (if endpoints and ports are not emitted) "root decoders":: nest under buses "port decoders":: nest under ports, or buses (if ports are not emitted) "endpoint decoders":: nest under endpoints, or ports (if endpoints are not emitted) or buses (if endpoints and ports are not emitted) Filters can be specified as either a single identifier, a space separated quoted string, or a comma separated list. When multiple filter identifiers are specified within a filter string, like "-m mem0,mem1,mem2", they are combined as an 'OR' filter. When multiple filter string types are specified, like "-m mem0,mem1,mem2 -p port10", they are combined as an 'AND' filter. So, "-m mem0,mem1,mem2 -p port10" would only list objects that are beneath port10 AND map mem0, mem1, OR mem2. Given that many topology queries seek to answer questions relative to a given memdev, buses, ports, endpoints, and decoders can be filtered by one or more memdevs. For example: ---- # cxl list -P -p switch,endpoint -m mem0 [ { "port":"port1", "host":"ACPI0016:00", "endpoints:port1":[ { "endpoint":"endpoint2", "host":"mem0" } ] } ] ---- Additionally, when provisioning new interleave configurations it is useful to know which memdevs can be referenced by a given decoder like a root decoder, or mapped by a given port if the decoders are not configured. ---- # cxl list -Mu -d decoder0.0 { "memdev":"mem0", "pmem_size":"256.00 MiB (268.44 MB)", "serial":"0", "host":"0000:35:00.0" } ---- Note that for the -m/--memdev=, -p/--port=, and -e/--endpoint= filters a host device name can be substituted for the CXL object identifier. For -m/--memdev= this is an endpoint PCI device name of the form "DDDD:bb:dd.f" (D: Domain b: Bus d: Device f: Function) (see /sys/bus/pci/devices), for -p/--port= this is an upstream switch port PCI device name of the form "DDDD:bb:dd.f", or a PCI bus name of the form "pciDDDD:bb", and for -e/--endpoint= the host device is CXL memory device object name of the form "memX". The --human option in addition to reformatting some fields to more human friendly strings also unwraps the array to reduce the number of lines of output. EXAMPLE ------- ---- # cxl list --memdevs [ { "memdev":"mem0", "pmem_size":268435456, "serial":0, "host":"0000:35:00.0" } ] # cxl list -BMu [ { "anon memdevs":[ { "memdev":"mem0", "pmem_size":"256.00 MiB (268.44 MB)", "serial":"0" } ] }, { "buses":[ { "bus":"root0", "provider":"ACPI.CXL" } ] } ] ---- OPTIONS ------- -m:: --memdev=:: Specify CXL memory device name(s) ("mem0"), device id(s) ("0"), and/or host device name(s) ("0000:35:00.0") to filter the listing. For example: ---- # cxl list -M --memdev="0 mem3 5" [ { "memdev":"mem0", "pmem_size":268435456, "serial":0 }, { "memdev":"mem3", "pmem_size":268435456, "ram_size":268435456, "serial":2 }, { "memdev":"mem5", "pmem_size":268435456, "ram_size":268435456, "serial":4 } ] ---- -s:: --serial=:: Specify CXL memory device serial number(s) to filter the listing -M:: --memdevs:: Include CXL memory devices in the listing -i:: --idle:: Include idle (not enabled / zero-sized) devices in the listing -H:: --health:: Include health information in the memdev listing. Example listing: ---- # cxl list -m mem0 -H [ { "memdev":"mem0", "pmem_size":268435456, "ram_size":268435456, "health":{ "maintenance_needed":true, "performance_degraded":true, "hw_replacement_needed":true, "media_normal":false, "media_not_ready":false, "media_persistence_lost":false, "media_data_lost":true, "media_powerloss_persistence_loss":false, "media_shutdown_persistence_loss":false, "media_persistence_loss_imminent":false, "media_powerloss_data_loss":false, "media_shutdown_data_loss":false, "media_data_loss_imminent":false, "ext_life_used":"normal", "ext_temperature":"critical", "ext_corrected_volatile":"warning", "ext_corrected_persistent":"normal", "life_used_percent":15, "temperature":25, "dirty_shutdowns":10, "volatile_errors":20, "pmem_errors":30 } } ] ---- -I:: --partition:: Include partition information in the memdev listing. Example listing: ---- # cxl list -m mem0 -I [ { "memdev":"mem0", "ram_size":273535729664, "partition_info":{ "total_size":273535729664, "volatile_only_size":0, "persistent_only_size":0, "partition_alignment_size":268435456 "active_volatile_size":273535729664, "active_persistent_size":0, "next_volatile_size":0, "next_persistent_size":0, } } ] ---- -A:: --alert-config:: Include alert configuration in the memdev listing. Example listing: ---- # cxl list -m mem0 -A [ { "memdev":"mem0", "pmem_size":0, "ram_size":273535729664, "alert_config":{ "life_used_prog_warn_threshold_valid":false, "dev_over_temperature_prog_warn_threshold_valid":false, "dev_under_temperature_prog_warn_threshold_valid":false, "corrected_volatile_mem_err_prog_warn_threshold_valid":true, "corrected_pmem_err_prog_warn_threshold_valid":false, "life_used_prog_warn_threshold_writable":false, "dev_over_temperature_prog_warn_threshold_writable":false, "dev_under_temperature_prog_warn_threshold_writable":false, "corrected_volatile_mem_err_prog_warn_threshold_writable":true, "corrected_pmem_err_prog_warn_threshold_writable":false, "life_used_crit_alert_threshold":0, "life_used_prog_warn_threshold":0, "dev_over_temperature_crit_alert_threshold":0, "dev_under_temperature_crit_alert_threshold":0, "dev_over_temperature_prog_warn_threshold":0, "dev_under_temperature_prog_warn_threshold":0, "corrected_volatile_mem_err_prog_warn_threshold":0, "corrected_pmem_err_prog_warn_threshold":0 }, } ] ---- -B:: --buses:: Include 'bus' / CXL root object(s) in the listing. Typically, on ACPI systems the bus object is a singleton associated with the ACPI0017 device, but there are test scenarios where there may be multiple CXL memory hierarchies. ---- # cxl list -B [ { "bus":"root3", "provider":"cxl_test" }, { "bus":"root0", "provider":"ACPI.CXL" } ] ---- -b:: --bus=:: Specify CXL root device name(s), device id(s), and / or CXL bus provider names to filter the listing. The supported provider names are "ACPI.CXL" and "cxl_test". -P:: --ports:: Include port objects (CXL / PCIe root ports + Upstream Switch Ports) in the listing. -p:: --port=:: Specify CXL Port device name(s) ("port2"), device id(s) ("2"), host device name(s) ("pci0000:34"), and / or port type name(s) to filter the listing. The supported port type names are "root" and "switch". Note that a bus object is also a port, so the following two syntaxes are equivalent: ---- # cxl list -B # cxl list -P -p root -S ---- ...where the '-S/--single' is required since descendant ports are always included in a port listing and '-S/--single' stops after listing the bus. Additionally, endpoint objects are ports so the following commands are equivalent, and no '-S/--single' is required as endpoint ports are terminal: ---- # cxl list -E # cxl list -P -p endpoint ---- By default, only 'switch' ports are listed, i.e. ---- # cxl list -P # cxl list -P -p switch ---- ...are equivalent. -S:: --single:: Specify whether the listing should emit all the objects that are descendants of a port that matches the port filter, or only direct descendants of the individual ports that match the filter. By default all descendant objects are listed. -E:: --endpoints:: Include endpoint objects (CXL Memory Device decoders) in the listing. ---- # cxl list -E [ { "endpoint":"endpoint2", "host":"mem0" } ] ---- -e:: --endpoint:: Specify CXL endpoint device name(s), or device id(s) to filter the emitted endpoint(s). -D:: --decoders:: Include decoder objects (CXL Memory decode capability instances in buses, ports, and endpoints) in the listing. -d:: --decoder:: Specify CXL decoder device name(s), device id(s), or decoder type names to filter the emitted decoder(s). The format for a decoder name is "decoder.". The possible decoder type names are "root", "switch", or "endpoint", similar to the port filter syntax. -T:: --targets:: Extend decoder listings with downstream port target information, port and bus listings with the downstream port information, and / or regions with mapping information. ---- # cxl list -BTu -b ACPI.CXL { "bus":"root0", "provider":"ACPI.CXL", "nr_dports":1, "dports":[ { "dport":"ACPI0016:00", "alias":"pci0000:34", "id":"0" } ] } ---- -R:: --regions:: Include region objects in the listing. -X:: --dax:: Append DAX information to region listings ---- # cxl list -RXu { "region":"region4", "resource":"0xf010000000", "size":"512.00 MiB (536.87 MB)", "interleave_ways":2, "interleave_granularity":4096, "decode_state":"commit", "daxregion":{ "id":4, "size":"512.00 MiB (536.87 MB)", "align":2097152, "devices":[ { "chardev":"dax4.0", "size":"512.00 MiB (536.87 MB)", "target_node":0, "align":2097152, "mode":"system-ram", "online_memblocks":0, "total_memblocks":4 } ] } } ---- -r:: --region:: Specify CXL region device name(s), or device id(s), to filter the listing. -L:: --media-errors:: Include media-error information. The poison list is retrieved from the device(s) and media_error records are added to the listing. Apply this option to memdevs and regions where devices support the poison list capability. "offset:" is relative to the region resource when listing by region and is the absolute device DPA when listing by memdev. "source:" is one of: External, Internal, Injected, Vendor Specific, or Unknown, as defined in CXL Specification v3.1 Table 8-140. ---- # cxl list -m mem9 --media-errors -u { "memdev":"mem9", "pmem_size":"1024.00 MiB (1073.74 MB)", "pmem_qos_class":42, "ram_size":"1024.00 MiB (1073.74 MB)", "ram_qos_class":42, "serial":"0x5", "numa_node":1, "host":"cxl_mem.5", "media_errors":[ { "offset":"0x40000000", "length":64, "source":"Injected" } ] } # cxl list -r region5 --media-errors -u { "region":"region5", "resource":"0xf110000000", "size":"2.00 GiB (2.15 GB)", "type":"pmem", "interleave_ways":2, "interleave_granularity":4096, "decode_state":"commit", "media_errors":[ { "offset":"0x1000", "length":64, "source":"Injected" }, { "offset":"0x2000", "length":64, "source":"Injected" } ] } ---- -v:: --verbose:: Increase verbosity of the output. This can be specified multiple times to be even more verbose on the informational and miscellaneous output, and can be used to override omitted flags for showing specific information. Note that cxl list --verbose --verbose is equivalent to cxl list -vv. - *-v* Enable --memdevs, --regions, --buses, --ports, --decoders, and --targets. - *-vv* Everything *-v* provides, plus include disabled devices with --idle. - *-vvv* Everything *-vv* provides, plus enable --health, --partition, and --media-errors. --debug:: If the cxl tool was built with debug enabled, turn on debug messages. include::human-option.txt[] include::../copyright.txt[] SEE ALSO -------- linkcxl:ndctl-list[1] ndctl-81/Documentation/cxl/cxl-monitor.txt000066400000000000000000000023671476737544500210610ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 cxl-monitor(1) ============== NAME ---- cxl-monitor - Monitor the CXL trace events SYNOPSIS -------- [verse] 'cxl monitor' [] DESCRIPTION ----------- cxl-monitor is used for monitoring the CXL trace events emitted by the kernel and convert them to json objects and dumping the json format notifications to standard output or a logfile. EXAMPLES -------- Run a monitor as a daemon to monitor events and output to a log file. [verse] cxl monitor --daemon --log=/var/log/cxl-monitor.log Run a monitor as a one-shot command and output the notifications to stdio. [verse] cxl monitor Run a monitor daemon as a system service [verse] systemctl start cxl-monitor.service OPTIONS ------- -l:: --log=:: Send log messages to the specified destination. - "": Send log messages to specified . - "standard": Send messages to standard output. The default log destination is '/var/log/cxl-monitor.log' if "--daemon" is specified, otherwise 'standard'. Note that standard and relative path for will not work if "--daemon" is specified. --daemon:: Run a monitor as a daemon. include::verbose-option.txt[] include::human-option.txt[] include::../copyright.txt[] SEE ALSO -------- linkcxl:cxl-list[1] ndctl-81/Documentation/cxl/cxl-read-labels.txt000066400000000000000000000011551476737544500215370ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 cxl-read-labels(1) ================== NAME ---- cxl-read-labels - read out the label area on a CXL memdev SYNOPSIS -------- [verse] 'cxl read-labels' [..] [] include::labels-description.txt[] This command dumps the raw binary data in a memdev's label area to stdout or a file. In the multi-memdev case the data is concatenated. OPTIONS ------- include::labels-options.txt[] include::bus-option.txt[] -o:: --output:: output file include::../copyright.txt[] SEE ALSO -------- linkcxl:cxl-write-labels[1], linkcxl:cxl-zero-labels[1], CXL-2.0 9.13.2 ndctl-81/Documentation/cxl/cxl-reserve-dpa.txt000066400000000000000000000042501476737544500216000ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 cxl-reserve-dpa(1) ================== NAME ---- cxl-reserve-dpa - allocate device-physical address space SYNOPSIS -------- [verse] 'cxl reserve-dpa' [..] [] The CXL region provisioning process proceeds in multiple steps. One of the steps is identifying and reserving the DPA span that each member of the interleave-set (region) contributes in advance of attaching that allocation to a region. For development, test, and debug purposes this command is a helper to find the next available decoder on endpoint (memdev) and mark a span of DPA as busy. OPTIONS ------- :: include::memdev-option.txt[] include::bus-option.txt[] -d:: --decoder:: Specify the decoder to attempt the allocation. The CXL specification mandates that allocations must be ordered by DPA and decoder instance. I.e. the lowest DPA allocation on the device is covered by decoder0, and the last / highest DPA allocation is covered by the last decoder. This ordering is enforced by the kernel. By default the tool picks the 'next available' decoder. -t:: --type:: Select the partition for the allocation. CXL devices implement a partition that divdes 'ram' and 'pmem' capacity, where 'pmem' capacity consumes the higher DPA capacity above the partition boundary. The type defaults to 'pmem'. Note that given CXL DPA allocation constraints, once any 'pmem' allocation is established then all remaining 'ram' capacity becomes reserved (skipped). -f:: --force:: The kernel enforces CXL DPA allocation ordering constraints, and the tool anticipates those and fails operations that are expected to fail without sending them to the kernel. For test purposes, continue to attempt "expected to fail" operations to exercise the driver. -s:: --size:: Specify the size of the allocation. This option supports the suffixes "k" or "K" for KiB, "m" or "M" for MiB, "g" or "G" for GiB and "t" or "T" for TiB. This defaults to "all available capacity of the specified type". -v:: Turn on verbose debug messages in the library (if libcxl was built with logging and debug enabled). include::../copyright.txt[] SEE ALSO -------- linkcxl:cxl-free-dpa[1] ndctl-81/Documentation/cxl/cxl-set-alert-config.txt000066400000000000000000000117141476737544500225310ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 cxl-set-alert-config(1) ======================= NAME ---- cxl-set-alert-config - set the warning alert threshold on a CXL memdev SYNOPSIS -------- [verse] 'cxl set-alert-config [..] []' DESCRIPTION ----------- CXL device raises an alert when its health status is changed. Critical alert shall automatically be configured by the device after a device reset. If supported, programmable warning thresholds also be initialized to vendor recommended defaults, then could be configured by the user. Use this command to configure warning alert thresholds of a device. Having issued this command, the newly requested warning thresholds would override the previously programmed warning thresholds. To enable warning alert, set both 'threshold=value' and 'alert=on'. To disable warning alert, set only 'alert=off'. Other cases would cause errors. Use "cxl list -m -A" to examine the programming warning threshold capabilities of a device. EXAMPLES -------- Set warning threshold to 30 and enable alert for life used. ---- # cxl set-alert-config mem0 --life-used-threshold=30 --life-used-alert=on {   "memdev":"mem0",   "ram_size":"1024.00 MiB (1073.74 MB)",   "alert_config":{     "life_used_prog_warn_threshold_valid":true,     "dev_over_temperature_prog_warn_threshold_valid":false,     "dev_under_temperature_prog_warn_threshold_valid":false,     "corrected_volatile_mem_err_prog_warn_threshold_valid":false,     "corrected_pmem_err_prog_warn_threshold_valid":false,     "life_used_prog_warn_threshold_writable":true,     "dev_over_temperature_prog_warn_threshold_writable":true,     "dev_under_temperature_prog_warn_threshold_writable":true,     "corrected_volatile_mem_err_prog_warn_threshold_writable":true,     "corrected_pmem_err_prog_warn_threshold_writable":true,     "life_used_crit_alert_threshold":75,     "life_used_prog_warn_threshold":30,     "dev_over_temperature_crit_alert_threshold":0,     "dev_under_temperature_crit_alert_threshold":0,     "dev_over_temperature_prog_warn_threshold":0,     "dev_under_temperature_prog_warn_threshold":0,     "corrected_volatile_mem_err_prog_warn_threshold":0,     "corrected_pmem_err_prog_warn_threshold":0   },   "serial":"0",   "host":"0000:0d:00.0" } cxl memdev: cmd_set_alert_config: set alert configuration 1 mem ---- Disable warning alert for life_used. ---- # cxl set-alert-config mem0 --life-used-alert=off {   "memdev":"mem0",   "ram_size":"1024.00 MiB (1073.74 MB)",   "alert_config":{     "life_used_prog_warn_threshold_valid":false,     "dev_over_temperature_prog_warn_threshold_valid":false,     "dev_under_temperature_prog_warn_threshold_valid":false,     "corrected_volatile_mem_err_prog_warn_threshold_valid":false,     "corrected_pmem_err_prog_warn_threshold_valid":false,     "life_used_prog_warn_threshold_writable":true,     "dev_over_temperature_prog_warn_threshold_writable":true,     "dev_under_temperature_prog_warn_threshold_writable":true,     "corrected_volatile_mem_err_prog_warn_threshold_writable":true,     "corrected_pmem_err_prog_warn_threshold_writable":true,     "life_used_crit_alert_threshold":75,     "life_used_prog_warn_threshold":30,     "dev_over_temperature_crit_alert_threshold":0,     "dev_under_temperature_crit_alert_threshold":0,     "dev_over_temperature_prog_warn_threshold":0,     "dev_under_temperature_prog_warn_threshold":0,     "corrected_volatile_mem_err_prog_warn_threshold":0,     "corrected_pmem_err_prog_warn_threshold":0   },   "serial":"0",   "host":"0000:0d:00.0" } cxl memdev: cmd_set_alert_config: set alert configuration 1 mem ---- OPTIONS ------- :: include::memdev-option.txt[] -L:: --life-used-threshold=:: Set for the life used warning alert threshold. --life-used-alert=:: Enable or disable the life used warning alert. Options are 'on' or 'off'. -O:: --over-temperature-threshold=:: Set for the device over temperature warning alert threshold. --over-temperature-alert=:: Enable or disable the device over temperature warning alert. Options are 'on' or 'off'. -U:: --under-temperature-threshold=:: Set for the device under temperature warning alert threshold. --under-temperature-alert=:: Enable or disable the device under temperature warning alert. Options are 'on' or 'off'. -V:: --volatile-mem-err-threshold=:: Set for the corrected volatile memory error warning alert threshold. --volatile-mem-err-alert=:: Enable or disable the corrected volatile memory error warning alert. Options are 'on' or 'off'. -P:: --pmem-err-threshold=:: Set for the corrected persistent memory error warning alert threshold. --pmem-err-alert=:: Enable or disable the corrected persistent memory error warning alert. Options are 'on' or 'off'. -v:: --verbose:: Turn on verbose debug messages in the library (if libcxl was built with logging and debug enabled). SEE ALSO -------- CXL-3.0 8.2.9.8.3.3 ndctl-81/Documentation/cxl/cxl-set-partition.txt000066400000000000000000000037711476737544500221740ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 cxl-set-partition(1) ==================== NAME ---- cxl-set-partition - set the partitioning between volatile and persistent capacity on a CXL memdev SYNOPSIS -------- [verse] 'cxl set-partition [ [..] []' DESCRIPTION ----------- CXL devices may support both volatile and persistent memory capacity. The amount of device capacity set aside for each type is typically established at the factory, but some devices also allow for dynamic re-partitioning. Use this command to partition a device into volatile and persistent capacity. The change in partitioning becomes the “next†configuration, to become active on the next device reset. Use "cxl list -m -I" to examine the partitioning capabilities of a device. A partition_alignment_size value of zero means there is no available capacity and therefore the partitions cannot be changed. Using this command to change the size of the persistent capacity shall result in the loss of data stored. OPTIONS ------- :: include::memdev-option.txt[] include::bus-option.txt[] -t:: --type=:: Type of partition, 'pmem' or 'ram' (volatile), to modify. Default: 'pmem' -s:: --size=:: Size of the partition in bytes. Size must align to the devices alignment requirement. Use 'cxl list -m -I' to find 'partition_alignment_size', or, use the --align option. Default: All available capacity is assigned to . -a:: --align:: Select this option to allow the automatic alignment of --size to meet device alignment requirements. When using this option, specify the minimum --size of the --type partition needed. When this option is omitted, the command fails if --size is not properly aligned. Use 'cxl list -m -I' to examine the partition_alignment_size. -v:: Turn on verbose debug messages in the library (if libcxl was built with logging and debug enabled). include::../copyright.txt[] SEE ALSO -------- linkcxl:cxl-list[1], CXL-2.0 8.2.9.5.2 ndctl-81/Documentation/cxl/cxl-update-firmware.txt000066400000000000000000000037341476737544500224650ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 cxl-update-firmware(1) ====================== NAME ---- cxl-update-firmware - update the firmware on a CXL memdev SYNOPSIS -------- [verse] 'cxl update-firmware [..] []' DESCRIPTION ----------- Update the firmware on one or more CXL mem devices. The mem devices must support the set of firmware related mailbox commands. This command doesn't directly issue the transfer / activate firmware mailbox commands. Instead, the kernel's firmware loader facility is used to provide the kernel with the data, and the kernel handles performing the actual update while also managing time slicing the transfer w.r.t. other background commands. EXAMPLE ------- ---- # cxl update-firmware mem0 -F firmware.bin -w [ { "memdev":"mem0", "pmem_size":1073741824, "ram_size":1073741824, "serial":0, "numa_node":0, "host":"cxl_mem.0", "firmware":{ "num_slots":3, "active_slot":2, "online_activate_capable":false, "slot_1_version":"cxl_test_fw_001", "slot_2_version":"cxl_test_fw_002", "slot_3_version":"cxl_test_new_fw", "fw_update_in_progress":false } } ] firmware update completed on 1 mem device ---- OPTIONS ------- include::bus-option.txt[] -F:: --firmware-file:: Firmware image file to use for the update. -c:: --cancel:: Attempt to abort an in-progress firmware update. This may fail depending on what stage the update process is in. -w:: --wait:: By default, the update-firmware command only initiates the firmware update, and returns, while the update operation happens asynchronously in the background. This option makes the firmware update command synchronous by waiting for it to complete before returning. If --wait is passed in without an accompanying firmware-file, it will initiate a wait on any current in-progress firmware updates, and then return. include::verbose-option.txt[] include::../copyright.txt[] SEE ALSO -------- linkcxl:cxl-list[1], ndctl-81/Documentation/cxl/cxl-wait-sanitize.txt000066400000000000000000000012761476737544500221600ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 cxl-wait-sanitize(1) ==================== NAME ---- cxl-wait-sanitize - wait for a sanitize operation to complete SYNOPSIS -------- [verse] 'cxl wait-sanitize [..] []' DESCRIPTION ----------- A sanitize operation can take several seconds to complete. Block and wait for the sanitize operation to complete. EXAMPLE ------- ---- # cxl wait-sanitize mem0 sanitize completed on 1 mem device ---- OPTIONS ------- include::bus-option.txt[] -t:: --timeout:: Milliseconds to wait before timing out and returning. Defaults to infinite. include::verbose-option.txt[] include::../copyright.txt[] SEE ALSO -------- linkcxl:cxl-list[1], ndctl-81/Documentation/cxl/cxl-write-labels.txt000066400000000000000000000013131476737544500217520ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 cxl-write-labels(1) =================== NAME ---- cxl-write-labels - write data to the label area on a memdev SYNOPSIS -------- [verse] 'cxl write-labels [-i ]' include::labels-description.txt[] Read data from the input filename, or stdin, and write it to the given device. Note that the device must not be active in any region, or actively registered with the nvdimm subsystem. If it is, the kernel will not allow write access to the device's label data area. OPTIONS ------- include::labels-options.txt[] include::bus-option.txt[] -i:: --input:: input file SEE ALSO -------- linkcxl:cxl-read-labels[1], linkcxl:cxl-zero-labels[1], CXL-2.0 9.13.2 ndctl-81/Documentation/cxl/cxl-zero-labels.txt000066400000000000000000000007731476737544500216100ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 cxl-zero-labels(1) ================== NAME ---- cxl-zero-labels - zero out the label area on a set of memdevs SYNOPSIS -------- [verse] 'cxl zero-labels' [..] [] include::labels-description.txt[] This command resets the device to its default state by deleting all labels. OPTIONS ------- include::labels-options.txt[] include::../copyright.txt[] SEE ALSO -------- linkcxl:cxl-read-labels[1], linkcxl:cxl-write-labels[1], CXL-2.0 9.13.2 ndctl-81/Documentation/cxl/cxl.txt000066400000000000000000000010171476737544500173630ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 cxl(1) ====== NAME ---- cxl - Provides enumeration and provisioning commands for CXL platforms SYNOPSIS -------- [verse] 'cxl' [--version] [--help] COMMAND [ARGS] OPTIONS ------- -v:: --version:: Display the version of the 'cxl' utility. -h:: --help:: Run the 'cxl help' command. DESCRIPTION ----------- The cxl utility provides enumeration and provisioning commands for the CXL devices managed by the Linux kernel. include::../copyright.txt[] SEE ALSO -------- linkcxl:ndctl[1] ndctl-81/Documentation/cxl/debug-option.txt000066400000000000000000000001531476737544500211710ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 --debug:: Turn on additional debug messages including library debug. ndctl-81/Documentation/cxl/decoder-option.txt000066400000000000000000000002611476737544500215100ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 -d:: --decoder=:: The root decoder to limit the operation to. Only regions that are children of the specified decoder will be acted upon. ndctl-81/Documentation/cxl/human-option.txt000066400000000000000000000004441476737544500212160ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 -u:: --human:: By default the command will output machine-friendly raw-integer data. Instead, with this flag, numbers representing storage size will be formatted as human readable strings with units, other fields are converted to hexadecimal strings. ndctl-81/Documentation/cxl/labels-description.txt000066400000000000000000000004401476737544500223570ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 DESCRIPTION ----------- The region label area is a small persistent partition of capacity available on some CXL memory devices. The label area is used to and configure or determine the set of memory devices participating in different interleave sets. ndctl-81/Documentation/cxl/labels-options.txt000066400000000000000000000006301476737544500215300ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 :: include::memdev-option.txt[] -s:: --size=:: Limit the operation to the given number of bytes. A size of 0 indicates to operate over the entire label capacity. -O:: --offset=:: Begin the operation at the given offset into the label area. -v:: Turn on verbose debug messages in the library (if libcxl was built with logging and debug enabled). ndctl-81/Documentation/cxl/lib/000077500000000000000000000000001476737544500166035ustar00rootroot00000000000000ndctl-81/Documentation/cxl/lib/cxl_new.txt000066400000000000000000000013561476737544500210100ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-2.0 cxl_new(3) ========== NAME ---- cxl_new - Create a new library context object that acts as a handle for all library operations SYNOPSIS -------- [verse] ---- #include int cxl_new(struct cxl_ctx **ctx); ---- DESCRIPTION ----------- Instantiates a new library context, and stores an opaque pointer in ctx. The context is freed by linklibcxl:cxl_unref[3], i.e. cxl_new(3) implies an internal linklibcxl:cxl_ref[3]. RETURN VALUE ------------ Returns 0 on success, and a negative errno on failure. Possible error codes are: * -ENOMEM * -ENXIO EXAMPLE ------- See example usage in test/libcxl.c include::../../copyright.txt[] SEE ALSO -------- linklibcxl:cxl_ref[3], linklibcxl:cxl_unref[3] ndctl-81/Documentation/cxl/lib/libcxl.txt000066400000000000000000000646431476737544500206360ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-2.0 libcxl(3) ========= NAME ---- libcxl - A library to interact with CXL devices through sysfs(5) and ioctl(2) interfaces SYNOPSIS -------- [verse] #include cc ... -lcxl DESCRIPTION ----------- libcxl provides interfaces to interact with CXL devices in Linux, using sysfs interfaces for most kernel interactions, and the ioctl() interface for command submission. The starting point for all library interfaces is a 'cxl_ctx' object, returned by linklibcxl:cxl_new[3]. CXL 'Type 3' memory devices and other CXL device objects are descendants of the cxl_ctx object, and can be iterated via an object an iterator API of the form cxl__foreach(, ). MEMDEVS ------- The object representing a CXL memory expander (Type 3 device) is 'struct cxl_memdev'. Library interfaces related to these devices have the prefix 'cxl_memdev_'. These interfaces are mostly associated with sysfs interactions (unless otherwise noted in their respective documentation sections). They are typically used to retrieve data published by the kernel, or to send data or trigger kernel operations for a given device. === MEMDEV: Enumeration ---- struct cxl_memdev *cxl_memdev_get_first(struct cxl_ctx *ctx); struct cxl_memdev *cxl_memdev_get_next(struct cxl_memdev *memdev); struct cxl_ctx *cxl_memdev_get_ctx(struct cxl_memdev *memdev); const char *cxl_memdev_get_host(struct cxl_memdev *memdev) struct cxl_memdev *cxl_endpoint_get_memdev(struct cxl_endpoint *endpoint); #define cxl_memdev_foreach(ctx, memdev) \ for (memdev = cxl_memdev_get_first(ctx); \ memdev != NULL; \ memdev = cxl_memdev_get_next(memdev)) ---- CXL memdev instances are enumerated from the global library context 'struct cxl_ctx'. By default a memdev only offers a portal to submit memory device commands, see the port, decoder, and endpoint APIs to determine what if any CXL Memory Resources are reachable given a specific memdev. The host of a memdev is the PCIe Endpoint device that registered its CXL capabilities with the Linux CXL core. === MEMDEV: Attributes ---- int cxl_memdev_get_id(struct cxl_memdev *memdev); unsigned long long cxl_memdev_get_serial(struct cxl_memdev *memdev); const char *cxl_memdev_get_devname(struct cxl_memdev *memdev); int cxl_memdev_get_major(struct cxl_memdev *memdev); int cxl_memdev_get_minor(struct cxl_memdev *memdev); unsigned long long cxl_memdev_get_pmem_size(struct cxl_memdev *memdev); unsigned long long cxl_memdev_get_ram_size(struct cxl_memdev *memdev); const char *cxl_memdev_get_firmware_version(struct cxl_memdev *memdev); size_t cxl_memdev_get_label_size(struct cxl_memdev *memdev); int cxl_memdev_nvdimm_bridge_active(struct cxl_memdev *memdev); int cxl_memdev_get_numa_node(struct cxl_memdev *memdev); int cxl_memdev_wait_sanitize(struct cxl_memdev *memdev); ---- A memdev is given a kernel device name of the form "mem%d" where an id (cxl_memdev_get_id()) is dynamically allocated as devices are discovered. Note that there are no guarantees that ids / kernel device names for memdevs are stable from one boot to the next, devices are enumerated asynchronously. If a stable identifier is use cxl_memdev_get_serial() which returns a value according to the 'Device Serial Number Extended Capability' in the PCIe 5.0 Base Specification. The character device node for command submission can be found by default at /dev/cxl/mem%d, or created with a major / minor returned from cxl_memdev_get_{major,minor}(). The 'pmem_size' and 'ram_size' attributes return the current provisioning of DPA (Device Physical Address / local capacity) in the device. cxl_memdev_get_numa_node() returns the affinitized CPU node number if available or -1 otherwise. cxl_memdev_wait_sanitize() if a sanitize operation is in-flight when this is called the program will block until the sanitize operation completes or the wait times out. === MEMDEV: Control ---- int cxl_memdev_disable_invalidate(struct cxl_memdev *memdev); int cxl_memdev_enable(struct cxl_memdev *memdev); ---- When a memory device is disabled it unregisters its associated endpoints and potentially intervening switch ports if there are no other memdevs pinning that port active. That means that any existing port objects that the library has previously returned are in valid and need to be re-read. Callers must be careful to re-retrieve port objects after cxl_memdev_disable_invalidate(). Any usage of a previously obtained port object after a cxl_memdev_disable_invalidate() call is a use-after-free programming error. It follows that after cxl_memdev_enable() new ports may appear in the topology that were not previously enumerable. NOTE: cxl_memdev_disable_invalidate() will force disable the memdev regardless of whether the memory provided by the device is in active use by the operating system. Callers take responsibility for assuring that it is safe to disable the memory device. Otherwise, this call can be as destructive as ripping a DIMM out of a running system. Like all other libcxl calls that mutate the system state or divulge security sensitive information this call requires root / CAP_SYS_ADMIN. === MEMDEV: Commands ---- struct cxl_cmd *cxl_cmd_new_raw(struct cxl_memdev *memdev, int opcode); struct cxl_cmd *cxl_cmd_new_identify(struct cxl_memdev *memdev); struct cxl_cmd *cxl_cmd_new_get_health_info(struct cxl_memdev *memdev); struct cxl_cmd *cxl_cmd_new_get_alert_config(struct cxl_memdev *memdev); struct cxl_cmd *cxl_cmd_new_set_alert_config(struct cxl_memdev *memdev); struct cxl_cmd *cxl_cmd_new_read_label(struct cxl_memdev *memdev, unsigned int offset, unsigned int length); struct cxl_cmd *cxl_cmd_new_write_label(struct cxl_memdev *memdev, void *buf, unsigned int offset, unsigned int length); int cxl_memdev_zero_label(struct cxl_memdev *memdev, size_t length, size_t offset); int cxl_memdev_read_label(struct cxl_memdev *memdev, void *buf, size_t length, size_t offset); int cxl_memdev_write_label(struct cxl_memdev *memdev, void *buf, size_t length, size_t offset); struct cxl_cmd *cxl_cmd_new_get_partition(struct cxl_memdev *memdev); struct cxl_cmd *cxl_cmd_new_set_partition(struct cxl_memdev *memdev, unsigned long long volatile_size); ---- A 'cxl_cmd' is a reference counted object which is used to perform 'Mailbox' commands as described in the CXL Specification. A 'cxl_cmd' object is tied to a 'cxl_memdev'. Associated library interfaces have the prefix 'cxl_cmd_'. Within this sub-class of interfaces, there are: * 'cxl_cmd_new_*()' interfaces that allocate a new cxl_cmd object for a given command type targeted at a given memdev. As part of the command instantiation process the library validates that the command is supported by the memory device, otherwise it returns NULL to indicate 'no support'. The libcxl command id is translated by the kernel into a CXL standard opcode. See the potential command ids in /usr/include/linux/cxl_mem.h. * 'cxl_cmd__set_' interfaces that set specific fields in a cxl_cmd * 'cxl_cmd_submit' which submits the command via ioctl() * 'cxl_cmd__get_' interfaces that get specific fields out of the command response * 'cxl_cmd_get_*' interfaces to get general command related information. cxl_cmd_new_raw() supports so called 'RAW' commands where the command id is 'RAW' and it carries an unmodified CXL memory device command payload associated with the 'opcode' argument. Given the kernel does minimal input validation on these commands typically raw commands are not supported by the kernel outside debug build scenarios. libcxl is limited to supporting commands that appear in the CXL standard / public specifications. cxl_memdev{read,write,zero}_label() are helpers for marshaling multiple label access commands over an arbitrary extent of the device's label area. cxl_cmd_partition_set_mode() supports selecting NEXTBOOT or IMMEDIATE mode. When CXL_SETPART_IMMEDIATE mode is set, it is the caller’s responsibility to avoid immediate changes to partitioning when the device is in use. When CXL_SETPART_NEXTBOOT mode is set, the change in partitioning shall become the “next†configuration, to become active on the next device reset. BUSES ----- The CXL Memory space is CPU and Device coherent. The address ranges that support coherent access are described by platform firmware and communicated to the operating system via a CXL root object 'struct cxl_bus'. === BUS: Enumeration ---- struct cxl_bus *cxl_bus_get_first(struct cxl_ctx *ctx); struct cxl_bus *cxl_bus_get_next(struct cxl_bus *bus); struct cxl_ctx *cxl_bus_get_ctx(struct cxl_bus *bus); struct cxl_bus *cxl_memdev_get_bus(struct cxl_memdev *memdev); struct cxl_bus *cxl_port_get_bus(struct cxl_port *port); struct cxl_bus *cxl_endpoint_get_bus(struct cxl_endpoint *endpoint); #define cxl_bus_foreach(ctx, bus) \ for (bus = cxl_bus_get_first(ctx); bus != NULL; \ bus = cxl_bus_get_next(bus)) ---- When a memdev is active it has established a CXL port hierarchy between itself and the root of its associated CXL topology. The cxl_{memdev,endpoint}_get_bus() helpers walk that topology to retrieve the associated bus object. === BUS: Attributes ---- const char *cxl_bus_get_provider(struct cxl_bus *bus); const char *cxl_bus_get_devname(struct cxl_bus *bus); int cxl_bus_get_id(struct cxl_bus *bus); ---- The provider name of a bus is a persistent name that is independent of discovery order. The possible provider names are 'ACPI.CXL' and 'cxl_test'. The devname and id attributes, like other objects, are just the kernel device names that are subject to change based on discovery order. === BUS: Control ---- int cxl_bus_disable_invalidate(struct cxl_bus *bus); ---- An entire CXL topology can be torn down with this API. Like other _invalidate APIs callers must assume that all library objects have been freed. This one goes one step further and also frees the @bus argument. This may crash the system and is only useful in kernel driver development scenarios. PORTS ----- CXL ports track the PCIe hierarchy between a platform firmware CXL root object, through CXL / PCIe Host Bridges, CXL / PCIe Root Ports, and CXL / PCIe Switch Ports. === PORT: Enumeration ---- struct cxl_port *cxl_bus_get_port(struct cxl_bus *bus); struct cxl_port *cxl_port_get_first(struct cxl_port *parent); struct cxl_port *cxl_port_get_next(struct cxl_port *port); struct cxl_port *cxl_port_get_parent(struct cxl_port *port); struct cxl_ctx *cxl_port_get_ctx(struct cxl_port *port); const char *cxl_port_get_host(struct cxl_port *port); struct cxl_port *cxl_decoder_get_port(struct cxl_decoder *decoder); struct cxl_port *cxl_port_get_next_all(struct cxl_port *port, const struct cxl_port *top); struct cxl_port *cxl_dport_get_port(struct cxl_dport *dport); #define cxl_port_foreach(parent, port) \ for (port = cxl_port_get_first(parent); port != NULL; \ port = cxl_port_get_next(port)) #define cxl_port_foreach_all(top, port) \ for (port = cxl_port_get_first(top); port != NULL; \ port = cxl_port_get_next_all(port, top)) ---- A bus object encapsulates a CXL port object. Use cxl_bus_get_port() to use generic port APIs on root objects. Ports are hierarchical. All but the a root object have another CXL port as a parent object retrievable via cxl_port_get_parent(). The root port of a hiearchy can be retrieved via any port instance in that hierarchy via cxl_port_get_bus(). The host of a port is the corresponding device name of the PCIe Root Port, or Switch Upstream Port with CXL capabilities. The cxl_port_foreach_all() helper does a depth first iteration of all ports beneath the 'top' port argument. === PORT: Control --- int cxl_port_disable_invalidate(struct cxl_port *port); int cxl_port_enable(struct cxl_port *port); --- cxl_port_disable_invalidate() is a violent operation that disables entire sub-tree of CXL Memory Device and Ports, only use it for test / debug scenarios, or ensuring that all impacted devices are deactivated first. === PORT: Attributes ---- const char *cxl_port_get_devname(struct cxl_port *port); int cxl_port_get_id(struct cxl_port *port); int cxl_port_is_enabled(struct cxl_port *port); bool cxl_port_is_root(struct cxl_port *port); bool cxl_port_is_switch(struct cxl_port *port); bool cxl_port_is_endpoint(struct cxl_port *port); int cxl_port_get_depth(struct cxl_port *port); bool cxl_port_hosts_memdev(struct cxl_port *port, struct cxl_memdev *memdev); int cxl_port_get_nr_dports(struct cxl_port *port); int cxl_port_decoders_committed(struct cxl_port *port); ---- The port type is communicated via cxl_port_is_(). An 'enabled' port is one that has succeeded in discovering the CXL component registers in the host device and has enumerated its downstream ports. In order for a memdev to be enabled for CXL memory operation all CXL ports in its ancestry must also be enabled including a root port, an arbitrary number of intervening switch ports, and a terminal endpoint port. cxl_port_hosts_memdev() returns true if the port's host appears in the memdev host's device topology ancestry. ==== DPORTS A CXL dport object represents a CXL / PCIe Switch Downstream Port, or a CXL / PCIe host bridge. ===== DPORT: Enumeration ---- struct cxl_dport *cxl_dport_get_first(struct cxl_port *port); struct cxl_dport *cxl_dport_get_next(struct cxl_dport *dport); struct cxl_dport *cxl_port_get_dport_by_memdev(struct cxl_port *port, struct cxl_memdev *memdev); #define cxl_dport_foreach(port, dport) \ for (dport = cxl_dport_get_first(port); dport != NULL; \ dport = cxl_dport_get_next(dport)) ---- ===== DPORT: Attributes ---- const char *cxl_dport_get_devname(struct cxl_dport *dport); const char *cxl_dport_get_physical_node(struct cxl_dport *dport); int cxl_dport_get_id(struct cxl_dport *dport); bool cxl_dport_maps_memdev(struct cxl_dport *dport, struct cxl_memdev *memdev); ---- The id of a dport is the hardware identifier used by an upstream port to reference a downstream port. The physical node of a dport is only available for platform firmware defined downstream ports and alias the companion object, like a PCI host bridge, in the PCI device hierarchy. The cxl_dport_maps_memdev() helper checks if a dport is an ancestor of a given memdev. ENDPOINTS --------- CXL endpoint objects encapsulate the set of host-managed device-memory (HDM) decoders in a physical memory device. The endpoint is the last hop in a decoder chain that translate SPA to DPA (system-physical-address to device-local-physical-address). === ENDPOINT: Enumeration ---- struct cxl_endpoint *cxl_endpoint_get_first(struct cxl_port *parent); struct cxl_endpoint *cxl_endpoint_get_next(struct cxl_endpoint *endpoint); struct cxl_ctx *cxl_endpoint_get_ctx(struct cxl_endpoint *endpoint); struct cxl_port *cxl_endpoint_get_parent(struct cxl_endpoint *endpoint); struct cxl_port *cxl_endpoint_get_port(struct cxl_endpoint *endpoint); const char *cxl_endpoint_get_host(struct cxl_endpoint *endpoint); struct cxl_endpoint *cxl_memdev_get_endpoint(struct cxl_memdev *memdev); struct cxl_endpoint *cxl_port_to_endpoint(struct cxl_port *port); #define cxl_endpoint_foreach(port, endpoint) \ for (endpoint = cxl_endpoint_get_first(port); endpoint != NULL; \ endpoint = cxl_endpoint_get_next(endpoint)) ---- === ENDPOINT: Attributes ---- const char *cxl_endpoint_get_devname(struct cxl_endpoint *endpoint); int cxl_endpoint_get_id(struct cxl_endpoint *endpoint); int cxl_endpoint_is_enabled(struct cxl_endpoint *endpoint); ---- DECODERS -------- Decoder objects are associated with the "HDM Decoder Capability" published in Port devices and CXL capable PCIe endpoints. The kernel additionally models platform firmware described CXL memory ranges (like the ACPI CEDT.CFMWS) as static decoder objects. They route System Physical Addresses through a port topology to an endpoint decoder that does the final translation from SPA to DPA (system-physical-address to device-local-physical-address). === DECODER: Enumeration ---- struct cxl_decoder *cxl_decoder_get_first(struct cxl_port *port); struct cxl_decoder *cxl_decoder_get_next(struct cxl_decoder *decoder); struct cxl_ctx *cxl_decoder_get_ctx(struct cxl_decoder *decoder); struct cxl_decoder *cxl_target_get_decoder(struct cxl_target *target); #define cxl_decoder_foreach(port, decoder) \ for (decoder = cxl_decoder_get_first(port); decoder != NULL; \ decoder = cxl_decoder_get_next(decoder)) ---- The definition of a CXL port in libcxl is an object that hosts one or more CXL decoder objects. === DECODER: Attributes ---- unsigned long long cxl_decoder_get_resource(struct cxl_decoder *decoder); unsigned long long cxl_decoder_get_size(struct cxl_decoder *decoder); unsigned long long cxl_decoder_get_dpa_resource(struct cxl_decoder *decoder); unsigned long long cxl_decoder_get_dpa_size(struct cxl_decoder *decoder); int cxl_decoder_set_dpa_size(struct cxl_decoder *decoder, unsigned long long size); const char *cxl_decoder_get_devname(struct cxl_decoder *decoder); int cxl_decoder_get_id(struct cxl_decoder *decoder); int cxl_decoder_get_nr_targets(struct cxl_decoder *decoder); struct cxl_region *cxl_decoder_get_region(struct cxl_decoder *decoder); enum cxl_decoder_target_type { CXL_DECODER_TTYPE_UNKNOWN, CXL_DECODER_TTYPE_EXPANDER, CXL_DECODER_TTYPE_ACCELERATOR, }; cxl_decoder_get_target_type(struct cxl_decoder *decoder); enum cxl_decoder_mode { CXL_DECODER_MODE_NONE, CXL_DECODER_MODE_MIXED, CXL_DECODER_MODE_PMEM, CXL_DECODER_MODE_RAM, }; enum cxl_decoder_mode cxl_decoder_get_mode(struct cxl_decoder *decoder); int cxl_decoder_set_mode(struct cxl_decoder *decoder, enum cxl_decoder_mode mode); bool cxl_decoder_is_pmem_capable(struct cxl_decoder *decoder); bool cxl_decoder_is_volatile_capable(struct cxl_decoder *decoder); bool cxl_decoder_is_mem_capable(struct cxl_decoder *decoder); bool cxl_decoder_is_accelmem_capable(struct cxl_decoder *decoder); bool cxl_decoder_is_locked(struct cxl_decoder *decoder); ---- The kernel protects the enumeration of the physical address layout of the system. Without CAP_SYS_ADMIN cxl_decoder_get_resource() returns ULLONG_MAX to indicate that the address information was not retrievable. Otherwise, cxl_decoder_get_resource() returns the currently programmed value of the base of the decoder's decode range. A zero-sized decoder indicates a disabled decoder. Root level decoders only support limited set of memory types in their address range. The cxl_decoder_is__capable() helpers identify what is supported. Switch level decoders, in contrast are capable of routing any memory type, i.e. they just forward along the memory type support from their parent port. Endpoint decoders follow the capabilities of their host memory device. The capabilities of a decoder are not to be confused with their type / mode. The type ultimately depends on the endpoint. For example an accelerator requires all decoders in its ancestry to be set to CXL_DECODER_TTYPE_ACCELERATOR, and conversely plain memory expander devices require CXL_DECODER_TTYPE_EXPANDER. Platform firmware may setup the CXL decode hierarchy before the OS boots, and may additionally require that the OS not change the decode settings. This property is indicated by the cxl_decoder_is_locked() API. When a decoder is associated with a region cxl_decoder_get_region() returns that region object. Note that it is only applicable to switch and endpoint decoders as root decoders have a 1:N relationship with regions. Use cxl_region_foreach() for the similar functionality for root decoders. ==== TARGETS A root or switch level decoder takes an SPA (system-physical-address) as input and routes it to a downstream port. Which downstream port depends on the downstream port's position in the interleave. A 'struct cxl_target' object represents the properties of a given downstream port relative to its interleave configuration. ===== TARGET: Enumeration ---- struct cxl_target *cxl_decoder_get_target_by_memdev(struct cxl_decoder *decoder, struct cxl_memdev *memdev); struct cxl_target * cxl_decoder_get_target_by_position(struct cxl_decoder *decoder, int position); struct cxl_target *cxl_target_get_first(struct cxl_decoder *decoder); struct cxl_target *cxl_target_get_next(struct cxl_target *target); #define cxl_target_foreach(decoder, target) \ for (target = cxl_target_get_first(decoder); target != NULL; \ target = cxl_target_get_next(target)) ---- Target objects can only be enumerated if the decoder has been configured, for switch decoders. For root decoders they are always available since the root decoder target mapping is static. The cxl_decoder_get_target_by_memdev() helper walks the topology to validate if the given memory device is capable of receiving cycles from this upstream decoder. It does not validate if the memory device is currently configured to participate in that decode. ===== TARGET: Attributes ---- int cxl_target_get_position(struct cxl_target *target); unsigned long cxl_target_get_id(struct cxl_target *target); const char *cxl_target_get_devname(struct cxl_target *target); bool cxl_target_maps_memdev(struct cxl_target *target, struct cxl_memdev *memdev); const char *cxl_target_get_physical_node(struct cxl_target *target); ---- The position of a decoder along with the interleave granularity dictate which address in the decoder's resource range map to which port. The target id is an identifier that the CXL port uses to reference this downstream port. For CXL / PCIe downstream switch ports the id is defined by the PCIe Link Capability Port Number field. For root decoders the id is specified by platform firmware specific mechanism. For ACPI.CXL defined root ports the id comes from the CEDT.CHBS / ACPI0016 _UID. The device name of a target is the name of the host device for the downstream port. For CXL / PCIe downstream ports the devname is downstream switch port PCI device. For CXL root ports the devname is a platform firmware object for the host bridge like a ACPI0016 device instance. The cxl_target_maps_memdev() helper is the companion of cxl_decoder_get_target_by_memdev() to determine which downstream ports / targets are capable of mapping which memdevs. Some platform firmware implementations define an alias / companion device to represent the root of a PCI device hierarchy. The cxl_target_get_physical_node() helper returns the device name of that companion object in the PCI hierarchy. ==== REGIONS A CXL region is composed of one or more slices of CXL memdevs, with configurable interleave settings - both the number of interleave ways, and the interleave granularity. In terms of hierarchy, it is the child of a CXL root decoder. A root decoder (recall that this corresponds to an ACPI CEDT.CFMWS 'window'), may have multiple child regions, but a region is strictly tied to one root decoder. The slices that compose a region are called mappings. A mapping is a tuple of 'memdev', 'endpoint decoder', and the 'position'. ===== REGION: Enumeration ---- struct cxl_region *cxl_region_get_first(struct cxl_decoder *decoder); struct cxl_region *cxl_region_get_next(struct cxl_region *region); #define cxl_region_foreach(decoder, region) \ for (region = cxl_region_get_first(decoder); region != NULL; \ region = cxl_region_get_next(region)) #define cxl_region_foreach_safe(decoder, region, _region) \ for (region = cxl_region_get_first(decoder), \ _region = region ? cxl_region_get_next(region) : NULL; \ region != NULL; \ region = _region, \ _region = _region ? cxl_region_get_next(_region) : NULL) ---- ===== REGION: Attributes ---- int cxl_region_get_id(struct cxl_region *region); const char *cxl_region_get_devname(struct cxl_region *region); void cxl_region_get_uuid(struct cxl_region *region, uuid_t uu); unsigned long long cxl_region_get_size(struct cxl_region *region); enum cxl_decoder_mode cxl_region_get_mode(struct cxl_region *region); unsigned long long cxl_region_get_resource(struct cxl_region *region); unsigned int cxl_region_get_interleave_ways(struct cxl_region *region); unsigned int cxl_region_get_interleave_granularity(struct cxl_region *region); struct cxl_decoder *cxl_region_get_target_decoder(struct cxl_region *region, int position); int cxl_region_set_size(struct cxl_region *region, unsigned long long size); int cxl_region_set_uuid(struct cxl_region *region, uuid_t uu); int cxl_region_set_interleave_ways(struct cxl_region *region, unsigned int ways); int cxl_region_set_interleave_granularity(struct cxl_region *region, unsigned int granularity); int cxl_region_set_target(struct cxl_region *region, int position, struct cxl_decoder *decoder); int cxl_region_clear_target(struct cxl_region *region, int position); int cxl_region_clear_all_targets(struct cxl_region *region); int cxl_region_decode_commit(struct cxl_region *region); int cxl_region_decode_reset(struct cxl_region *region); struct daxctl_region *cxl_region_get_daxctl_region(struct cxl_region *region); ---- A region's resource attribute is the Host Physical Address at which the region's address space starts. The region's address space is a subset of the parent root decoder's address space. The interleave ways is the number of component memdevs participating in the region. The interleave granularity depends on the root decoder's granularity, and must follow the interleave math rules defined in the CXL spec. Regions have a list of targets 0..N, which are programmed with the name of an endpoint decoder under each participating memdev. The 'decode_commit' and 'decode_reset' attributes reserve and free DPA space on a given memdev by allocating an endpoint decoder, and programming it based on the region's interleave geometry. Once a region is active it is attached to either the NVDIMM subsystem where its properties can be interrogated by ndctl, or the DAX subsystem where its properties can be interrogated by daxctl. The helper cxl_region_get_daxctl_region() returns an 'struct daxctl_region *' that can be used with other libdaxctl APIs. include::../../copyright.txt[] SEE ALSO -------- linklibcxl:cxl[1] ndctl-81/Documentation/cxl/lib/meson.build000066400000000000000000000040431476737544500207460ustar00rootroot00000000000000if get_option('asciidoctor').enabled() asciidoc_conf = custom_target('asciidoctor-extensions.rb', command : [ 'sed', '-e', 's,@Utility@,Libcxl,g', '-e', 's,@utility@,cxl,g', '@INPUT@' ], input : '../../asciidoctor-extensions.rb.in', output : 'asciidoctor-extensions.rb', capture : true, ) else asciidoc_conf = custom_target('asciidoc.conf', command : [ 'sed', '-e', 's,UTILITY,libcxl,g', ], input : '../../asciidoc.conf.in', output : 'asciidoc.conf', capture : true, ) endif filedeps = [ '../../copyright.txt', ] libcxl_manpages = [ 'libcxl.txt', 'cxl_new.txt', ] foreach man : libcxl_manpages name = man.split('.')[0] output = name + '.3' output_xml = name + '.xml' if get_option('asciidoctor').enabled() custom_target(name, command : [ asciidoc, '-b', 'manpage', '-d', 'manpage', '-acompat-mode', '-I', '@OUTDIR@', '-rasciidoctor-extensions', '-amansource=libcxl', '-amanmanual=libcxl Manual', '-andctl_version=@0@'.format(meson.project_version()), '-o', '@OUTPUT@', '@INPUT@' ], input : man, output : output, depend_files : filedeps, depends : asciidoc_conf, install : get_option('docs').enabled(), install_dir : join_paths(get_option('mandir'), 'man3'), ) else xml = custom_target(output_xml, command : [ asciidoc, '-b', 'docbook', '-d', 'manpage', '-f', asciidoc_conf, '--unsafe', '-andctl_version=@0@'.format(meson.project_version()), '-o', '@OUTPUT@', '@INPUT@', ], input : man, output : output_xml, depend_files : filedeps, depends : asciidoc_conf, ) xsl = files('../../manpage-normal.xsl') custom_target(name, command : [ xmlto, '-o', '@OUTDIR@', '-m', xsl, 'man', '@INPUT@' ], depends : xml, depend_files : xsl, input : xml, output : output, install : get_option('docs').enabled(), install_dir : join_paths(get_option('mandir'), 'man3'), ) endif endforeach ndctl-81/Documentation/cxl/memdev-option.txt000066400000000000000000000005011476737544500213550ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 A 'memX' device name, or a memdev id number. Restrict the operation to the specified memdev(s). The keyword 'all' can be specified to indicate the lack of any restriction. -S:: --serial:: Rather an a memdev id number, interpret the argument(s) as a list of serial numbers. ndctl-81/Documentation/cxl/meson.build000066400000000000000000000052141476737544500202010ustar00rootroot00000000000000if get_option('asciidoctor').enabled() asciidoc_conf = custom_target('asciidoctor-extensions.rb', command : [ 'sed', '-e', 's,@Utility@,Cxl,g', '-e', 's,@utility@,cxl,g', '@INPUT@' ], input : '../asciidoctor-extensions.rb.in', output : 'asciidoctor-extensions.rb', capture : true, ) else asciidoc_conf = custom_target('asciidoc.conf', command : [ 'sed', '-e', 's,UTILITY,cxl,g', ], input : '../asciidoc.conf.in', output : 'asciidoc.conf', capture : true, ) endif filedeps = [ '../copyright.txt', 'memdev-option.txt', 'labels-options.txt', 'debug-option.txt', 'region-description.txt', 'decoder-option.txt', 'xable-no-op.txt', ] cxl_manpages = [ 'cxl.txt', 'cxl-list.txt', 'cxl-read-labels.txt', 'cxl-write-labels.txt', 'cxl-zero-labels.txt', 'cxl-enable-memdev.txt', 'cxl-disable-memdev.txt', 'cxl-enable-port.txt', 'cxl-disable-port.txt', 'cxl-disable-bus.txt', 'cxl-set-partition.txt', 'cxl-reserve-dpa.txt', 'cxl-free-dpa.txt', 'cxl-create-region.txt', 'cxl-disable-region.txt', 'cxl-enable-region.txt', 'cxl-destroy-region.txt', 'cxl-monitor.txt', 'cxl-update-firmware.txt', 'cxl-set-alert-config.txt', 'cxl-wait-sanitize.txt', ] foreach man : cxl_manpages name = man.split('.')[0] output = name + '.1' output_xml = name + '.xml' if get_option('asciidoctor').enabled() custom_target(name, command : [ asciidoc, '-b', 'manpage', '-d', 'manpage', '-acompat-mode', '-I', '@OUTDIR@', '-rasciidoctor-extensions', '-amansource=cxl', '-amanmanual=cxl Manual', '-andctl_version=@0@'.format(meson.project_version()), '-o', '@OUTPUT@', '@INPUT@' ], input : man, output : output, depend_files : filedeps, depends : asciidoc_conf, install : get_option('docs').enabled(), install_dir : join_paths(get_option('mandir'), 'man1'), ) else xml = custom_target(output_xml, command : [ asciidoc, '-b', 'docbook', '-d', 'manpage', '-f', asciidoc_conf, '--unsafe', '-andctl_version=@0@'.format(meson.project_version()), '-o', '@OUTPUT@', '@INPUT@', ], input : man, output : output_xml, depend_files : filedeps, depends : asciidoc_conf, ) xsl = files('../manpage-normal.xsl') custom_target(name, command : [ xmlto, '-o', '@OUTDIR@', '-m', xsl, 'man', '@INPUT@' ], depends : xml, depend_files : xsl, input : xml, output : output, install : get_option('docs').enabled(), install_dir : join_paths(get_option('mandir'), 'man1'), ) endif endforeach subdir('lib') ndctl-81/Documentation/cxl/region-description.txt000066400000000000000000000003501476737544500224000ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 DESCRIPTION ----------- A CXL region is composed of one or more slices of CXL memdevs, with configurable interleave settings - both the number of interleave ways, and the interleave granularity. ndctl-81/Documentation/cxl/verbose-option.txt000066400000000000000000000001201476737544500215420ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 -v:: --verbose:: Emit more debug messages ndctl-81/Documentation/cxl/xable-no-op.txt000066400000000000000000000007021476737544500207160ustar00rootroot00000000000000// SPDX-License-Identifier: gpl-2.0 Given any enable or disable command, if the operation is a no-op due to the current state of a target (i.e. already enabled or disabled), it is still considered successful when executed even if no actual operation is performed. The target can be a bus, decoder, memdev, or region. The operation will still succeed, and report the number of bus/decoder/memdev/region operated on, even if the operation is a no-op. ndctl-81/Documentation/daxctl/000077500000000000000000000000001476737544500165265ustar00rootroot00000000000000ndctl-81/Documentation/daxctl/daxctl-create-device.txt000066400000000000000000000053221476737544500232460ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 daxctl-create-device(1) ======================= NAME ---- daxctl-create-device - Create a devdax device SYNOPSIS -------- [verse] 'daxctl create-device' [] EXAMPLES -------- * Creates dax0.1 with 4G of size ---- # daxctl create-device -s 4G [ { "chardev":"dax0.1", "size":4294967296, "target_node":0, "mode":"devdax" } ] ---- * Creates devices with fully available size on all regions ---- # daxctl create-device -u [ { "chardev":"dax0.1", "size":"15.63 GiB (16.78 GB)", "target_node":0, "mode":"devdax" }, { "chardev":"dax1.1", "size":"15.63 GiB (16.78 GB)", "target_node":1, "mode":"devdax" } ] ---- * Creates dax0.1 with fully available size on region id 0 ---- # daxctl create-device -r 0 -u { "chardev":"dax0.1", "size":"15.63 GiB (16.78 GB)", "target_node":0, "mode":"devdax" } ---- DESCRIPTION ----------- Creates dax device in 'devdax' mode in dynamic regions. The resultant can also be convereted to the 'system-ram' mode which arranges for the dax range to be hot-plugged into the system as regular memory. 'daxctl create-device' expects that the BIOS or kernel defines a range in the EFI memory map with EFI_MEMORY_SP. The resultant ranges mean that it's 100% capacity is reserved for applications. OPTIONS ------- include::region-option.txt[] -s:: --size=:: For regions that support dax device cretion, set the device size in bytes. Otherwise it defaults to the maximum size specified by region. This option supports the suffixes "k" or "K" for KiB, "m" or "M" for MiB, "g" or "G" for GiB and "t" or "T" for TiB. The size must be a multiple of the region alignment. -a:: --align:: Applications that want to establish dax memory mappings with page table entries greater than system base page size (4K on x86) need a device that is sufficiently aligned. This defaults to 2M. Note that "devdax" mode enforces all mappings to be aligned to this value, i.e. it fails unaligned mapping attempts. --input:: Applications that want to select ranges assigned to a device-dax instance, or wanting to establish previously created devices, can pass an input JSON file. The file option lets a user pass a JSON object similar to the one listed with "daxctl list". The device name is not re-created, but if a "chardev" is passed in the JSON file, it will use that to get the region id. Note that the JSON content in the file cannot be an array of JSON objects but rather a single JSON object i.e. without the array enclosing brackets. include::human-option.txt[] include::verbose-option.txt[] include::../copyright.txt[] SEE ALSO -------- linkdaxctl:daxctl-list[1],daxctl-reconfigure-device[1],daxctl-destroy-device[1] ndctl-81/Documentation/daxctl/daxctl-destroy-device.txt000066400000000000000000000015101476737544500234670ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 daxctl-destroy-device(1) ======================== NAME ---- daxctl-destroy-device - Destroy a devdax device SYNOPSIS -------- [verse] 'daxctl destroy-device' [...] [] EXAMPLES -------- * Destroys dax0.1 ---- # daxctl disable-device dax0.1 disabled 1 device # daxctl destroy-device dax0.1 destroyed 1 device ---- * Destroys all devices in region id 0 ---- # daxctl disable-device -r 0 all disabled 3 devices # daxctl destroy-device -r 0 all destroyed 2 devices ---- DESCRIPTION ----------- Destroys a dax device in 'devdax' mode. OPTIONS ------- include::region-option.txt[] include::human-option.txt[] include::verbose-option.txt[] include::../copyright.txt[] SEE ALSO -------- linkdaxctl:daxctl-list[1],daxctl-reconfigure-device[1],daxctl-create-device[1] ndctl-81/Documentation/daxctl/daxctl-disable-device.txt000066400000000000000000000012611476737544500234040ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 daxctl-disable-device(1) ======================== NAME ---- daxctl-disable-device - Disables a devdax device SYNOPSIS -------- [verse] 'daxctl disable-device' [] EXAMPLES -------- * Disables dax0.1 ---- # daxctl disable-device dax0.1 ---- * Disables all devices in region id 0 ---- # daxctl disable-device -r 0 all disabled 3 devices ---- DESCRIPTION ----------- Disables a dax device in 'devdax' mode. OPTIONS ------- include::region-option.txt[] include::human-option.txt[] include::verbose-option.txt[] include::../copyright.txt[] SEE ALSO -------- linkdaxctl:daxctl-list[1],daxctl-reconfigure-device[1],daxctl-create-device[1] ndctl-81/Documentation/daxctl/daxctl-enable-device.txt000066400000000000000000000013251476737544500232300ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 daxctl-enable-device(1) ======================= NAME ---- daxctl-enable-device - Enable a devdax device SYNOPSIS -------- [verse] 'daxctl enable-device' [...] [] EXAMPLES -------- * Enables dax0.1 ---- # daxctl enable-device dax0.1 enabled 1 device ---- * Enables all devices in region id 0 ---- # daxctl enable-device -r 0 all enabled 3 devices ---- DESCRIPTION ----------- Enables a dax device in 'devdax' mode. OPTIONS ------- include::region-option.txt[] include::human-option.txt[] include::verbose-option.txt[] include::../copyright.txt[] SEE ALSO -------- linkdaxctl:daxctl-list[1],daxctl-reconfigure-device[1],daxctl-create-device[1] ndctl-81/Documentation/daxctl/daxctl-list.txt000066400000000000000000000037001476737544500215170ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 daxctl-list(1) ============== NAME ---- daxctl-list - dump the platform Device-DAX regions, devices, and attributes in json. SYNOPSIS -------- [verse] 'daxctl list' [] Walk all the device-dax-regions in the system and list all device instances along with some of their major attributes. Options can be specified to limit the output to objects of a certain class. Where the classes are regions or devices. By default, 'daxctl list' with no options is equivalent to: [verse] daxctl list --devices EXAMPLE ------- ---- # daxctl list --regions --devices { "id":1, "devices":[ { "chardev":"dax1.0", "size":3233808384 } ] } ---- OPTIONS ------- -r:: --region=:: A device-dax region is a contiguous range of memory that hosts one or more /dev/daxX.Y devices, where X is the region id and Y is the device instance id. The keyword 'all' can be specified to carry out the operation on every region in the system. -d:: --dev=:: Specify a dax device name, . tuple, or keyword 'all' to filter the listing. For example to list the first device instance in region1: ---- # daxctl list --dev=1.0 { "chardev":"dax1.0", "size":3233808384 } ---- -D:: --devices:: Include device-dax instance info in the listing (default) -M:: --mappings:: Include device-dax instance mappings info in the listing -R:: --regions:: Include region info in the listing -i:: --idle:: Include idle (not enabled / zero-sized) devices in the listing -u:: --human:: By default 'daxctl list' will output machine-friendly raw-integer data. Instead, with this flag, numbers representing storage size will be formatted as human readable strings with units, other fields are converted to hexadecimal strings. Example: ---- # daxctl list { "chardev":"dax1.0", "size":32828817408 } # daxctl list --human { "chardev":"dax1.0", "size":"30.57 GiB (32.83 GB)" } ---- include::../copyright.txt[] ndctl-81/Documentation/daxctl/daxctl-migrate-device-model.txt000066400000000000000000000040011476737544500245220ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 daxctl-migrate-device-model(1) ============================== NAME ---- daxctl-migrate-device-model - Opt-in to the /sys/bus/dax device-model, allow for alternative Device-DAX instance drivers. SYNOPSIS -------- [verse] 'daxctl migrate-device-model' Arrange for modprobe to disable the dax_pmem_compat, if present, and instead deploy the dax_pmem module to convert to the /sys/bus/dax model. Kernel versions prior to v5.1 may not support /sys/bus/dax in which case the result of this command is a nop until the kernel is updated. The motivation for changing from /sys/class/dax to /sys/bus/dax is to allow for alternative drivers for Device-DAX instances, in particular the dax_kmem driver. By default device-dax publishes a /dev/daxX.Y character device for userspace to directly map performance differentiated memory. This is fine if the memory is to be exclusively consumed / managed by userspace. Alternatively an administrator may want the kernel to manage the memory, make it available via malloc(), allow for over-provisioning, and / or apply kernel-based resource control schemes to the memory. In that case the memory fronted by a given Device-DAX instance can be assigned to the dax_kmem driver which arranges for the core-kernel memory-management sub-system to assume management of the memory range. This behavior is opt-in for consideration of existing applications / scripts that may be hard coded to use /sys/class/dax. Fixes have been submitted to applications known to have these direct dependencies http://git.kernel.dk/cgit/fio/commit/?id=b08e7d6b18b4[FIO] https://github.com/pmem/pmdk/commit/91bc8620884e[PMDK], however, there may be others and a system-owner should be aware of the potential for regression of Device-DAX consuming scripts, applications, or older daxctl binaries. The modprobe policy established by this utility becomes effective after the next reboot, or after all DAX related modules have been removed and re-loaded with "udevadm trigger" include::../copyright.txt[] ndctl-81/Documentation/daxctl/daxctl-offline-memory.txt000066400000000000000000000034021476737544500234730ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 daxctl-offline-memory(1) ======================== NAME ---- daxctl-offline-memory - Offline the memory for a device that is in system-ram mode SYNOPSIS -------- [verse] 'daxctl offline-memory' [...] [] EXAMPLES -------- * Reconfigure dax0.0 to system-ram mode ---- # daxctl reconfigure-device --mode=system-ram --human dax0.0 { "chardev":"dax0.0", "size":"7.87 GiB (8.45 GB)", "target_node":2, "mode":"system-ram" } ---- * Offline the memory ---- # daxctl offline-memory dax0.0 dax0.0: 62 sections offlined offlined memory for 1 device ---- DESCRIPTION ----------- Offline the memory sections associated with a device that has been converted to the system-ram mode. If one or more blocks are already offline, attempt to offline the remaining blocks. If all blocks were already offline, print a message and return success without actually doing anything. This is complementary to the 'daxctl-online-memory' command, and may be used when it is wished to offline the memory sections, but not convert the device back to 'devdax' mode. OPTIONS ------- -r:: --region=:: Restrict the operation to devices belonging to the specified region(s). A device-dax region is a contiguous range of memory that hosts one or more /dev/daxX.Y devices, where X is the region id and Y is the device instance id. -u:: --human:: By default the command will output machine-friendly raw-integer data. Instead, with this flag, numbers representing storage size will be formatted as human readable strings with units, other fields are converted to hexadecimal strings. -v:: --verbose:: Emit more debug messages include::../copyright.txt[] SEE ALSO -------- linkdaxctl:daxctl-reconfigure-device[1],daxctl-online-memory[1] ndctl-81/Documentation/daxctl/daxctl-online-memory.txt000066400000000000000000000040331476737544500233360ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 daxctl-online-memory(1) ======================= NAME ---- daxctl-online-memory - Online the memory for a device that is in system-ram mode SYNOPSIS -------- [verse] 'daxctl online-memory' [...] [] EXAMPLES -------- * Reconfigure dax0.0 to system-ram mode, don't online the memory ---- # daxctl reconfigure-device --mode=system-ram --no-online --human dax0.0 { "chardev":"dax0.0", "size":"7.87 GiB (8.45 GB)", "target_node":2, "mode":"system-ram" } ---- * Online the memory separately ---- # daxctl online-memory dax0.0 dax0.0: 62 new sections onlined onlined memory for 1 device ---- * Onlining memory when some sections were already online ---- # daxctl online-memory dax0.0 dax0.0: 1 section already online dax0.0: 61 new sections onlined onlined memory for 1 device ---- DESCRIPTION ----------- Online the memory sections associated with a device that has been converted to the system-ram mode. If one or more blocks are already online, print a message about them, and attempt to online the remaining blocks. This is complementary to the 'daxctl-reconfigure-device' command, when used with the '--no-online' option to skip onlining memory sections immediately after the reconfigure. In these scenarios, the memory can be onlined at a later time using 'daxctl-online-memory'. OPTIONS ------- -r:: --region=:: Restrict the operation to devices belonging to the specified region(s). A device-dax region is a contiguous range of memory that hosts one or more /dev/daxX.Y devices, where X is the region id and Y is the device instance id. include::movable-options.txt[] -u:: --human:: By default the command will output machine-friendly raw-integer data. Instead, with this flag, numbers representing storage size will be formatted as human readable strings with units, other fields are converted to hexadecimal strings. -v:: --verbose:: Emit more debug messages include::../copyright.txt[] SEE ALSO -------- linkdaxctl:daxctl-reconfigure-device[1],daxctl-offline-memory[1] ndctl-81/Documentation/daxctl/daxctl-reconfigure-device.txt000066400000000000000000000261221476737544500243140ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 daxctl-reconfigure-device(1) ============================ NAME ---- daxctl-reconfigure-device - Reconfigure a dax device into a different mode SYNOPSIS -------- [verse] 'daxctl reconfigure-device' [...] [] DESCRIPTION ----------- Reconfigure the operational mode of a dax device. This can be used to convert a regular 'devdax' mode device to the 'system-ram' mode which arranges for the dax range to be hot-plugged into the system as regular memory. NOTE: This is a destructive operation. Any data on the dax device *will* be lost. NOTE: Device reconfiguration depends on the dax-bus device model. See linkdaxctl:daxctl-migrate-device-model[1] for more information. If dax-class is in use (via the dax_pmem_compat driver), the reconfiguration will fail with an error such as the following: ---- # daxctl reconfigure-device --mode=system-ram --region=0 all libdaxctl: daxctl_dev_disable: dax3.0: error: device model is dax-class dax3.0: disable failed: Operation not supported error reconfiguring devices: Operation not supported reconfigured 0 devices ---- 'daxctl-reconfigure-device' nominally expects that it will online new memory blocks as 'movable', so that kernel data doesn't make it into this memory. However, there are other potential agents that may be configured to automatically online new hot-plugged memory as it appears. Most notably, these are the '/sys/devices/system/memory/auto_online_blocks' configuration, or system udev rules. If such an agent races to online memory sections, daxctl checks if the blocks were onlined as 'movable' memory. If this was not the case, and the memory blocks are found to be in a different zone, then a warning is displayed. If it is desired that a different agent control the onlining of memory blocks, and the associated memory zone, then it is recommended to use the --no-online option described below. This will abridge the device reconfiguration operation to just hotplugging the memory, and refrain from then onlining it. In case daxctl detects that there is a kernel policy to auto-online blocks (via /sys/devices/system/memory/auto_online_blocks), then reconfiguring to system-ram will result in a failure. This can be overridden with '--force'. THEORY OF OPERATION ------------------- The kernel device-dax subsystem surfaces character devices that provide DAX-access (direct mappings sans page-cache buffering) to a given memory region. The devices are named /dev/daxX.Y where X is a region-id and Y is an instance-id within that region. There are 2 mechanisms that trigger device-dax instances to appear: 1. Persistent Memory (PMEM) namespace configured in "devdax" mode. See "ndctl create-namspace --help" and https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/dax/Kconfig[CONFIG_DEV_DAX_PMEM]. In this case the device-dax instance is statically sized to its host memory region which is bounded to the physical address range of the host namespace. 2. Soft Reserved memory enumerated by platform firmware. On EFI systems this is communicated via the so called EFI_MEMORY_SP "Special Purpose" attribute. See https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/dax/Kconfig[CONFIG_DEV_DAX_HMEM]. In this case the device-dax instance(s) associated with the given memory region can be resized and divided into multiple devices. In the Soft Reservation case the expectation for EFI + ACPI based platforms is that in addition to the EFI_MEMORY_SP attribute the firmware also creates distinct ACPI proximity domains for any address range that has different performance characteristics than default "System RAM". So, the SRAT will define the proximity domain, the SLIT communicates relative distance to other proximity domains, and the HMAT is populated with nominal read/write latency and read/write bandwidth data. That HMAT data is emitted to the kernel log on bootup, and also exported to sysfs. See https://www.kernel.org/doc/html/latest/admin-guide/mm/numaperf.html[NUMAPERF], for the runtime representation of CPU to Memory node performance details. Outside of the NUMA performance details linked above the other method to detect the presence of "Soft Reserved" memory is to dump /proc/iomem and look for "Soft Reserved" ranges. If the kernel was not built with CONFIG_EFI_SOFT_RESERVE, predates the introduction of CONFIG_EFI_SOFT_RESERVE (v5.5), or was booted with the efi=nosoftreserve command line then device-dax will not attach and the expectation is that the memory shows up as a memory-only NUMA node. Otherwise the memory shows up as a device-dax instance and DAXCTL(1) can be used to optionally partition it and assign the memory back to the kernel as "System RAM", or the device can be mapped directly as the back end of a userspace memory allocator like https://pmem.io/vmem/libvmem/[LIBVMEM]. EXAMPLES -------- * Reconfigure dax0.0 to system-ram mode, don't online the memory ---- # daxctl reconfigure-device --mode=system-ram --no-online dax0.0 [ { "chardev":"dax0.0", "size":16777216000, "target_node":2, "mode":"system-ram" } ] ---- * Reconfigure dax0.0 to devdax mode, attempt to offline the memory ---- # daxctl reconfigure-device --human --mode=devdax --force dax0.0 { "chardev":"dax0.0", "size":"15.63 GiB (16.78 GB)", "target_node":2, "mode":"devdax" } ---- * Reconfigure all dax devices on region0 to system-ram mode ---- # daxctl reconfigure-device --mode=system-ram --region=0 all [ { "chardev":"dax0.0", "size":16777216000, "target_node":2, "mode":"system-ram" }, { "chardev":"dax0.1", "size":16777216000, "target_node":3, "mode":"system-ram" } ] ---- * Run a process called 'some-service' using numactl to restrict its cpu nodes to '0' and '1', and memory allocations to node 2 (determined using daxctl_dev_get_target_node() or 'daxctl list') ---- # daxctl reconfigure-device --mode=system-ram dax0.0 [ { "chardev":"dax0.0", "size":16777216000, "target_node":2, "mode":"system-ram" } ] # numactl --cpunodebind=0-1 --membind=2 -- some-service --opt1 --opt2 ---- * Change the size of a dax device ---- # daxctl reconfigure-device dax0.1 -s 16G reconfigured 1 device # daxctl reconfigure-device dax0.1 -s 0 reconfigured 1 device ---- OPTIONS ------- include::region-option.txt[] -s:: --size=:: For regions that support dax device creation, change the device size in bytes. This option supports the suffixes "k" or "K" for KiB, "m" or "M" for MiB, "g" or "G" for GiB and "t" or "T" for TiB. The size must be a multiple of the region alignment. This option is mutually exclusive with -m or --mode. -a:: --align:: Applications that want to establish dax memory mappings with page table entries greater than system base page size (4K on x86) need a device that is sufficiently aligned. This defaults to 2M. Note that "devdax" mode enforces all mappings to be aligned to this value, i.e. it fails unaligned mapping attempts. This option is mutually exclusive with -m or --mode. -m:: --mode=:: Specify the mode to which the dax device(s) should be reconfigured. - "system-ram": hotplug the device into system memory. - "devdax": switch to the normal "device dax" mode. This requires the kernel to support hot-unplugging 'kmem' based memory. If this is not available, a reboot is the only way to switch back to 'devdax' mode. -N:: --no-online:: By default, memory sections provided by system-ram devices will be brought online automatically and immediately with the 'online_movable' policy. Use this option to disable the automatic onlining behavior. -C:: --check-config:: Get reconfiguration parameters from the global daxctl config file. This is typically used when daxctl-reconfigure-device is called from a systemd-udevd device unit file. The reconfiguration proceeds only if the match parameters in a 'reconfigure-device' section of the config match the dax device specified on the command line. See the 'PERSISTENT RECONFIGURATION' section for more details. include::movable-options.txt[] -f:: --force:: - When converting from "system-ram" mode to "devdax", it is expected that all the memory sections are first made offline. By default, daxctl won't touch online memory. However with this option, attempt to offline the memory on the NUMA node associated with the dax device before converting it back to "devdax" mode. - Additionally, if a kernel policy to auto-online blocks is detected, reconfiguration to system-ram fails. With this option, the failure can be overridden to allow reconfiguration regardless of kernel policy. Doing this may result in a successful reconfiguration, but it may not be possible to subsequently offline the memory without a reboot. include::human-option.txt[] include::verbose-option.txt[] PERSISTENT RECONFIGURATION -------------------------- The 'mode' of a daxctl device is not persistent across reboots by default. This is because the device itself does not hold any metadata that hints at what mode it was set to, or is intended to be used. The default mode for such a device on boot is 'devdax'. The administrator may set policy such that certain dax devices are always reconfigured into a target configuration every boot. This is accomplished via a daxctl config file. The config file may have multiple sections influencing different aspects of daxctl operation. The section of interest for persistent reconfiguration is 'reconfigure-device'. The format of this is as follows: ---- [reconfigure-device ] nvdimm.uuid = mode = (default: system-ram) online = (default: true) movable = (default: true) ---- Here is an example of a config snippet for managing three devdax namespaces, one is left in devdax mode, the second is changed to system-ram mode with default options (online, movable), and the third is set to system-ram mode, the memory is onlined, but not movable. Note that the 'subsection name' can be arbitrary, and is only used to identify a specific config section. It does not have to match the 'device name' (e.g. 'dax0.0' etc). ---- [reconfigure-device dax0] nvdimm.uuid = ed93e918-e165-49d8-921d-383d7b9660c5 mode = devdax [reconfigure-device dax1] nvdimm.uuid = f36d02ff-1d9f-4fb9-a5b9-8ceb10a00fe3 mode = system-ram [reconfigure-device dax2] nvdimm.uuid = f36d02ff-1d9f-4fb9-a5b9-8ceb10a00fe3 mode = system-ram online = true movable = false ---- The following example can be used to create a devdax mode namespace, and simultaneously add the newly created namespace to the config file for system-ram conversion. ---- ndctl create-namespace --mode=devdax | \ jq -r "\"[reconfigure-device $(uuidgen)]\", \"nvdimm.uuid = \(.uuid)\", \"mode = system-ram\"" >> $config_path ---- The default location for daxctl config files is under {daxctl_confdir}/, and any file with a '.conf' suffix at this location is considered. It is acceptable to have multiple files containing ini-style config sections, but the {section, subsection} tuple must be unique across all config files under {daxctl_confdir}/. include::../copyright.txt[] SEE ALSO -------- linkdaxctl:daxctl-list[1],daxctl-migrate-device-model[1] ndctl-81/Documentation/daxctl/daxctl.txt000066400000000000000000000012671476737544500205540ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 daxctl(1) ========= NAME ---- daxctl - Provides enumeration and provisioning commands for the Linux kernel Device-DAX facility SYNOPSIS -------- [verse] 'daxctl' [--version] [--help] COMMAND [ARGS] OPTIONS ------- -v:: --version:: Display daxctl version. -h:: --help:: Run daxctl help command. DESCRIPTION ----------- The daxctl utility provides enumeration and provisioning commands for the Linux kernel Device-DAX facility. This facility enables DAX mappings of performance / feature differentiated memory without need of a filesystem. include::../copyright.txt[] SEE ALSO -------- linkdaxctl:ndctl-create-namespace[1], linkdaxctl:ndctl-list[1] ndctl-81/Documentation/daxctl/human-option.txt000066400000000000000000000004441476737544500217070ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 -u:: --human:: By default the command will output machine-friendly raw-integer data. Instead, with this flag, numbers representing storage size will be formatted as human readable strings with units, other fields are converted to hexadecimal strings. ndctl-81/Documentation/daxctl/meson.build000066400000000000000000000051001476737544500206640ustar00rootroot00000000000000if get_option('asciidoctor').enabled() asciidoc_conf = custom_target('asciidoctor-extensions.rb', command : [ 'sed', '-e', 's,@Utility@,Daxctl,g', '-e', 's,@utility@,daxctl,g', '@INPUT@' ], input : '../asciidoctor-extensions.rb.in', output : 'asciidoctor-extensions.rb', capture : true, ) else asciidoc_conf = custom_target('asciidoc.conf', command : [ 'sed', '-e', 's,UTILITY,daxctl,g', ], input : '../asciidoc.conf.in', output : 'asciidoc.conf', capture : true, ) endif filedeps = [ 'human-option.txt', '../copyright.txt', ] daxctl_manpages = [ 'daxctl.txt', 'daxctl-list.txt', 'daxctl-migrate-device-model.txt', 'daxctl-reconfigure-device.txt', 'daxctl-online-memory.txt', 'daxctl-offline-memory.txt', 'daxctl-disable-device.txt', 'daxctl-enable-device.txt', 'daxctl-create-device.txt', 'daxctl-destroy-device.txt', ] foreach man : daxctl_manpages name = man.split('.')[0] output = name + '.1' output_xml = name + '.xml' if get_option('asciidoctor').enabled() custom_target(name, command : [ asciidoc, '-b', 'manpage', '-d', 'manpage', '-acompat-mode', '-I', '@OUTDIR@', '-rasciidoctor-extensions', '-amansource=daxctl', '-amanmanual=daxctl Manual', '-adaxctl_confdir=@0@'.format(daxctlconf_dir), '-adaxctl_conf=@0@'.format(daxctlconf), '-andctl_keysdir=@0@'.format(ndctlkeys_dir), '-andctl_version=@0@'.format(meson.project_version()), '-o', '@OUTPUT@', '@INPUT@' ], input : man, output : output, depend_files : filedeps, depends : asciidoc_conf, install : get_option('docs').enabled(), install_dir : join_paths(get_option('mandir'), 'man1'), ) else xml = custom_target(output_xml, command : [ asciidoc, '-b', 'docbook', '-d', 'manpage', '-f', asciidoc_conf, '--unsafe', '-adaxctl_confdir=@0@'.format(daxctlconf_dir), '-adaxctl_conf=@0@'.format(daxctlconf), '-andctl_keysdir=@0@'.format(ndctlkeys_dir), '-andctl_version=@0@'.format(meson.project_version()), '-o', '@OUTPUT@', '@INPUT@', ], input : man, output : output_xml, depend_files : filedeps, depends : asciidoc_conf, ) xsl = files('../manpage-normal.xsl') custom_target(name, command : [ xmlto, '-o', '@OUTDIR@', '-m', xsl, 'man', '@INPUT@' ], depends : xml, depend_files : xsl, input : xml, output : output, install : get_option('docs').enabled(), install_dir : join_paths(get_option('mandir'), 'man1'), ) endif endforeach ndctl-81/Documentation/daxctl/movable-options.txt000066400000000000000000000006361476737544500224120ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 --no-movable:: '--movable' is the default. This can be overridden to online new memory such that it is not 'movable'. This allows any allocation to potentially be served from this memory. This may preclude subsequent removal. With the '--movable' behavior (which is default), kernel allocations will not consider this memory, and it will be reserved for application use. ndctl-81/Documentation/daxctl/region-option.txt000066400000000000000000000004341476737544500220610ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 -r:: --region=:: Restrict the operation to devices belonging to the specified region(s). A device-dax region is a contiguous range of memory that hosts one or more /dev/daxX.Y devices, where X is the region id and Y is the device instance id. ndctl-81/Documentation/daxctl/verbose-option.txt000066400000000000000000000001201476737544500222330ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 -v:: --verbose:: Emit more debug messages ndctl-81/Documentation/manpage-base.xsl000066400000000000000000000026721476737544500203260ustar00rootroot00000000000000 sp br ndctl-81/Documentation/manpage-normal.xsl000066400000000000000000000013551476737544500207010ustar00rootroot00000000000000 \ . ndctl-81/Documentation/ndctl/000077500000000000000000000000001476737544500163535ustar00rootroot00000000000000ndctl-81/Documentation/ndctl/ars-description.txt000066400000000000000000000005731476737544500222270ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 DESCRIPTION ----------- NVDIMM Address Range Scrub is a capability provided by platform firmware that allows for the discovery of memory errors by system software. It enables system software to pre-emptively avoid accesses that could lead to uncorrectable memory error handling events, and it otherwise allows memory errors to be enumerated. ndctl-81/Documentation/ndctl/dimm-description.txt000066400000000000000000000015141476737544500223640ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 DESCRIPTION ----------- A generic DIMM device object, named /dev/nmemX, is registered for each memory device indicated in the ACPI NFIT table, or other platform NVDIMM resource discovery mechanism. The LIBNVDIMM core provides a built-in driver for these DIMM devices. The driver is responsible for determining if the DIMM implements a namespace label area, and initializing the kernel's in-memory copy of that label data. The kernel performs runtime modifications of that data when namespace provisioning actions are taken, and actively blocks userspace from initiating label data changes while the DIMM is active in any region. Disabling a DIMM, after all the regions it is a member of have been disabled, allows userspace to manually update the label data to be consumed when the DIMM is next enabled. ndctl-81/Documentation/ndctl/human-option.txt000066400000000000000000000004031476737544500215270ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 -u:: --human:: Format numbers representing storage sizes, or offsets as human readable strings with units instead of the default machine-friendly raw-integer data. Convert other numeric fields into hexadecimal strings. ndctl-81/Documentation/ndctl/intel-nvdimm-security.txt000066400000000000000000000145651476737544500233770ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 THEORY OF OPERATION ------------------- The Intel Device Specific Methods (DSM) specification v1.7 and v1.8 [1] introduced the following security management operations: enable passphrase, update passphrase, unlock DIMM, disable security, freeze security, secure (crypto) erase, overwrite, master passphrase enable, master passphrase update, and master passphrase secure erase. The security management for NVDIMMs is comprised of two parts. The front end uses the Linux key management framework (trusted and encrypted keys [2]) to store the encrypted passphrases in the kernel-managed keyring. The interface for this is the 'keyutils' utility which uses the key management APIs in the Linux kernel. The back end takes the decrypted payload (which is the DIMM passphrase) and passes it to the DIMM. Unlike other DSMs which are composed by libndctl and sent to the kernel via an ioctl, the security DSMs are managed through the 'security' sysfs attribute under the 'dimm' device. A 'key-ID' is written to the 'security' attribute and the kernel pulls the associated key material from the user keyring that is maintained by the kernel. The security process begins with the generation of a 'master key' that is used to seal (encrypt) the passphrase for the DIMM. There can either be one common 'master key' that is used to encrypt every DIMM's passphrase, or a separate key can be generated for each DIMM. The 'master key' is also referred to as the 'key-encryption-key' (kek). The 'kek' can either be generated by the TPM (Trusted Platform Module) on the system, or alternatively, the 'System Master Key' can also be used as the 'kek' For testing purposes a user key with randomized payload can also be used as a 'kek'. See [2] for details. To perform any security operations, it is expected that the 'kek' has been added to the kernel's user keyring as shown in example below: ---- # keyctl show Session Keyring 736023423 --alswrv 0 0 keyring: _ses 675104189 --alswrv 0 65534 \_ keyring: _uid.0 680187394 --alswrv 0 0 \_ trusted: nvdimm-master ---- Before performing any of the security operations, all the regions associated with the DIMM in question need to be disabled. For the 'overwrite' operation, in addition to the 'regions', the 'dimm' also needs to be disabled. [1] http://pmem.io/documents/NVDIMM_DSM_Interface-V1.8.pdf + [2] https://www.kernel.org/doc/Documentation/security/keys/trusted-encrypted.rst The following sub-sections describe specifics of each security feature. === UNLOCK Unlock is performed by the kernel, however a preparation step must happen before the unlock DSM can be issued by the kernel. It is expected that from the initramfs, a setup command (ndctl 'load-keys') is executed before the libnvdimm module is loaded by modprobe. This command will inject the 'kek' and the encrypted passphrases into the kernel's user keyring. During the 'probe' of the libnvdimm driver, it will: . Check the security state of the device and see if the DIMM is locked . Request the associated encrypted passphrase from the kernel's user key ring . Use the 'kek' to decrypt the passphrase . Create the unlock DSM, copy the decrypted payload into the DSM . Issue the DSM to unlock the DIMM If the DIMM is already unlocked, the kernel will attempt to revalidate the passphrase. If we fail to revalidate the passphrase, the kernel will freeze the security and disallow any further security configuration changes. A kernel module parameter is available to override this behavior. === SETUP USER PASSPHRASE To setup the passphrase for a DIMM, it is expected that the 'kek' to be used is present in the kernel's user keyring. The 'kek' encrypts the DIMM passphrase using the 'enc32' key format. The plaintext passphrase is never provided by or made visible to the user. It is instead randomly generated by the kernel and userspace does not have access to it. Upon encryption, a binary blob of the passphrase is written to the passphrase blob storage directory ({ndctl_keysdir}). The user is responsible for backing up the passphrase blobs to a secure location. === UPDATE USER PASSPHRASE The update user passphrase operation uses the same DSM command as enable user passphrase. Most of the work is done on the key management side. The user has the option of providing a new 'kek' for the new passphrase, but continuing to use the existing 'kek' is also acceptable. The following operations are performed for 'update-passphrase': . Remove the encrypted passphrase from the kernel's user keyring. . Rename the passphrase blob to old. . Load this old passphrase blob into the keyring with an "old" name. . Create the new passphrase and encrypt with the 'kek'. . Send DSM with the old and new decrypted passphrases. . Remove old passphrase and the passphrase blob from the keyring. === REMOVE USER PASSPHRASE The 'key-ID' for the passphrase to be removed is written to sysfs. The kernel then sends the DSM to disable security, and the passphrase is then removed from the keyring, and the associated passphrase blob is deleted. === CRYPTO (SECURE) ERASE This operation is similar to remove-passphrase. The kernel issues a WBINVD instruction before and after the operation to ensure no data corruption from a stale CPU cache. Use ndctl's sanitize-dimm command with the `--crypto-erase` option to perform this operation. === OVERWRITE This is invoked using `--overwrite` option for ndctl 'sanitize-dimm'. The overwrite operation wipes the entire NVDIMM. The operation can take a significant amount of time. NOTE: When the command returns successfully, it just means overwrite has been successfully started, and not that the overwrite is complete. Subsequently, 'ndctl wait-overwrite' can be used to wait for the NVDIMMs that are performing overwrite. Upon successful completion of an overwrite, the WBINVD instruction is issued by the kernel. If both --crypto-erase and --overwrite options are supplied, then crypto-erase is performed before overwrite. === SECURITY FREEZE This operation does not require a passphrase. This will cause any security command other than a status query to be locked out until the next boot. === MASTER PASSPHRASE SETUP, UPDATE, and CRYPTO ERASE These operations are similar to the user passphrase enable and update. The only difference is that a different passphrase is used. The master passphrase has no relation to the master key ('kek') which is used for encryption of either passphrase. ndctl-81/Documentation/ndctl/labels-description.txt000066400000000000000000000003601476737544500226760ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 DESCRIPTION ----------- The namespace label area is a small persistent partition of capacity available on some NVDIMM devices. The label area is used to provision one, or more, namespaces from regions. ndctl-81/Documentation/ndctl/labels-options.txt000066400000000000000000000007141476737544500220510ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 :: include::xable-dimm-options.txt[] -s:: --size=:: Limit the operation to the given number of bytes. A size of 0 indicates to operate over the entire label capacity. -O:: --offset=:: Begin the operation at the given offset into the label area. -b:: --bus=:: include::xable-bus-options.txt[] -v:: Turn on verbose debug messages in the library (if ndctl was built with logging and debug enabled). ndctl-81/Documentation/ndctl/meson.build000066400000000000000000000067051476737544500205250ustar00rootroot00000000000000if get_option('asciidoctor').enabled() asciidoc_conf = custom_target('asciidoctor-extensions.rb', command : [ 'sed', '-e', 's,@Utility@,Ndctl,g', '-e', 's,@utility@,ndctl,g', '@INPUT@' ], input : '../asciidoctor-extensions.rb.in', output : 'asciidoctor-extensions.rb', capture : true, ) else asciidoc_conf = custom_target('asciidoc.conf', command : [ 'sed', '-e', 's,UTILITY,ndctl,g', ], input : '../asciidoc.conf.in', output : 'asciidoc.conf', capture : true, ) endif filedeps = [ '../copyright.txt', 'region-description.txt', 'xable-region-options.txt', 'dimm-description.txt', 'xable-dimm-options.txt', 'xable-namespace-options.txt', 'ars-description.txt', 'labels-description.txt', 'labels-options.txt', ] ndctl_manpages = [ 'ndctl.txt', 'ndctl-wait-scrub.txt', 'ndctl-start-scrub.txt', 'ndctl-zero-labels.txt', 'ndctl-read-labels.txt', 'ndctl-write-labels.txt', 'ndctl-init-labels.txt', 'ndctl-check-labels.txt', 'ndctl-enable-region.txt', 'ndctl-disable-region.txt', 'ndctl-enable-dimm.txt', 'ndctl-disable-dimm.txt', 'ndctl-enable-namespace.txt', 'ndctl-disable-namespace.txt', 'ndctl-create-namespace.txt', 'ndctl-destroy-namespace.txt', 'ndctl-check-namespace.txt', 'ndctl-clear-errors.txt', 'ndctl-inject-error.txt', 'ndctl-inject-smart.txt', 'ndctl-update-firmware.txt', 'ndctl-list.txt', 'ndctl-monitor.txt', 'ndctl-setup-passphrase.txt', 'ndctl-update-passphrase.txt', 'ndctl-remove-passphrase.txt', 'ndctl-freeze-security.txt', 'ndctl-sanitize-dimm.txt', 'ndctl-load-keys.txt', 'ndctl-wait-overwrite.txt', 'ndctl-read-infoblock.txt', 'ndctl-write-infoblock.txt', 'ndctl-activate-firmware.txt', ] foreach man : ndctl_manpages name = man.split('.')[0] output = name + '.1' output_xml = name + '.xml' if get_option('asciidoctor').enabled() custom_target(name, command : [ asciidoc, '-b', 'manpage', '-d', 'manpage', '-acompat-mode', '-I', '@OUTDIR@', '-rasciidoctor-extensions', '-amansource=ndctl', '-amanmanual=ndctl Manual', '-andctl_confdir=@0@'.format(ndctlconf_dir), '-andctl_monitorconf=@0@'.format(ndctlconf), '-andctl_keysdir=@0@'.format(ndctlkeys_dir), '-andctl_version=@0@'.format(meson.project_version()), '-o', '@OUTPUT@', '@INPUT@' ], input : man, output : output, depend_files : filedeps, depends : asciidoc_conf, install : get_option('docs').enabled(), install_dir : join_paths(get_option('mandir'), 'man1'), ) else xml = custom_target(output_xml, command : [ asciidoc, '-b', 'docbook', '-d', 'manpage', '-f', asciidoc_conf, '--unsafe', '-andctl_version=@0@'.format(meson.project_version()), '-andctl_confdir=@0@'.format(ndctlconf_dir), '-andctl_monitorconf=@0@'.format(ndctlconf), '-andctl_keysdir=@0@'.format(ndctlkeys_dir), '-o', '@OUTPUT@', '@INPUT@', ], input : man, output : output_xml, depend_files : filedeps, depends : asciidoc_conf, ) xsl = files('../manpage-normal.xsl') custom_target(name, command : [ xmlto, '-o', '@OUTDIR@', '-m', xsl, 'man', '@INPUT@' ], depends : xml, depend_files : xsl, input : xml, output : output, install : get_option('docs').enabled(), install_dir : join_paths(get_option('mandir'), 'man1'), ) endif endforeach ndctl-81/Documentation/ndctl/namespace-description.txt000066400000000000000000000063671476737544500234050ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 THEORY OF OPERATION ------------------- The capacity of an NVDIMM REGION (contiguous span of persistent memory) is accessed via one or more NAMESPACE devices. REGION is the Linux term for what ACPI and UEFI call a DIMM-interleave-set, or a system-physical-address-range that is striped (by the memory controller) across one or more memory modules. The UEFI specification defines the 'NVDIMM Label Protocol' as the combination of label area access methods and a data format for provisioning one or more NAMESPACE objects from a REGION. Note that label support is optional and if Linux does not detect the label capability it will automatically instantiate a "label-less" namespace per region. Examples of label-less namespaces are the ones created by the kernel's 'memmap=ss!nn' command line option (see the nvdimm wiki on kernel.org), or NVDIMMs without a valid 'namespace index' in their label area. NOTE: Label-less namespaces lack many of the features of their label-rich cousins. For example, their size cannot be modified, or they cannot be fully 'destroyed' (i.e. the space reclaimed). A destroy operation will zero any mode-specific metadata. Finally, for create-namespace operations on label-less namespaces, ndctl bypasses the region capacity availability checks, and always satisfies the request using the full region capacity. The only reconfiguration operation supported on a label-less namespace is changing its 'mode'. A namespace can be provisioned to operate in one of 4 modes, 'fsdax', 'devdax', 'sector', and 'raw'. Here are the expected usage models for these modes: - fsdax: Filesystem-DAX mode is the default mode of a namespace when specifying 'ndctl create-namespace' with no options. It creates a block device (/dev/pmemX[.Y]) that supports the DAX capabilities of Linux filesystems (xfs and ext4 to date). DAX removes the page cache from the I/O path and allows mmap(2) to establish direct mappings to persistent memory media. The DAX capability enables workloads / working-sets that would exceed the capacity of the page cache to scale up to the capacity of persistent memory. Workloads that fit in page cache or perform bulk data transfers may not see benefit from DAX. When in doubt, pick this mode. - devdax: Device-DAX mode enables similar mmap(2) DAX mapping capabilities as Filesystem-DAX. However, instead of a block-device that can support a DAX-enabled filesystem, this mode emits a single character device file (/dev/daxX.Y). Use this mode to assign persistent memory to a virtual-machine, register persistent memory for RDMA, or when gigantic mappings are needed. - sector: Use this mode to host legacy filesystems that do not checksum metadata or applications that are not prepared for torn sectors after a crash. Expected usage for this mode is for small boot volumes. This mode is compatible with other operating systems. - raw: Raw mode is effectively just a memory disk that does not support DAX. Typically this indicates a namespace that was created by tooling or another operating system that did not know how to create a Linux 'fsdax' or 'devdax' mode namespace. This mode is compatible with other operating systems, but again, does not support DAX operation. ndctl-81/Documentation/ndctl/ndctl-activate-firmware.txt000066400000000000000000000105651476737544500236370ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 ndctl-activate-firmware(1) ========================== NAME ---- ndctl-activate-firmware - activate staged firmware on memory devices SYNOPSIS -------- [verse] 'ndctl activate-firmware' [ ... ] [] Some persistent memory devices run a firmware locally on the device / "DIMM" to perform tasks like media management, capacity provisioning, and health monitoring. The process of updating that firmware typically involves a reboot because it has implications for in-flight memory transactions. However, reboots can be costly for systems that can not tolerate extended downtime. The kernel detects platforms that expose support for runtime-firmware-activation (FWA). The 'ndctl update-firmware' stages new firmware binaries, but if the platform supports FWA it will additionally arm the devices for activation. Then 'ndctl activate-firmware' may attempt to activate the firmware live. However, if the platform indicates that the memory controller will be taken off-line for the duration of the update "activate_method == suspend" then the default policy for firmware activation is to inject a truncated hibernate cycle to freeze devices and applications before the hard quiesce is injected by the platform, and then resume the system. *DANGER* the activate-firmware command includes a --force option to tell the driver bypass the hibernation cycle and perform the update "live". I.e. it arranges for applications and devices to race the platform injected quiesce period. This option should only be used explicit knowledge that the platform quiesce time will not trigger completion timeout violations for any devices in the system. EXAMPLES -------- Check for any buses that support activation without triggering an activation: ---- # ndctl activate-firmware all --dry-run ACPI.NFIT: ndbus1: has no devices that support firmware update. nfit_test.1: ndbus3: has no devices that support firmware update. e820: ndbus0: has no devices that support firmware update. [ { "provider":"nfit_test.0", "dev":"ndbus1", "scrub_state":"idle", "firmware":{ "activate_method":"suspend", "activate_state":"idle" }, "dimms":[ { ... ---- Check that a specific bus supports activation without performing an activation: ---- # ndctl activate-firmware nfit_test.0 --dry-run --force [ { "provider":"nfit_test.0", "dev":"ndbus2", "scrub_state":"idle", "firmware":{ "activate_method":"suspend", "activate_state":"idle" }, "dimms":[ ... ] ---- The result is equivalent to 'ndctl list -BFDu' upon successful activation. The 'ndctl list' command can also enumerate the default activation method: ---- # ndctl list -b nfit_test.0 -BF [ { "provider":"nfit_test.0", "dev":"ndbus2", "scrub_state":"idle", "firmware":{ "activate_method":"suspend", "activate_state":"idle" } } ] ---- OPTIONS ------- -n:: --dry-run:: Perform all actions related to activation including honoring --idle and --force, but skip the final execution of the activation. The overrides are undone before the command completes. Any failed overrides will be reported as error messages. -I:: --idle:: Implied by default, this option controls whether the platform will attempt to increase the completion timeout of all devices in the system and validate that the max completion timeout satisfies the time needed to perform the activation. This validation step can be overridden by specifying --no-idle. -f:: --force:: The activation method defaults to the reported "bus.firmware.activate_method" property. When the method is "live" then this --force option is ignored. When the method is "reset" no runtime activation is attempted. When the method is "suspend" this option indicates to the driver to bypass the hibernate cycle to activate firmware. in the bus When the reported "activate_method" is "suspend" the kernel driver may support overriding the suspend requirement and instead issue the firmware-activation live. *CAUTION* this may lead to undefined system behavior if device completion timeouts are violated for in-flight memory operations. -v:: --verbose:: Emit debug messages for the firmware activation procedure include::../copyright.txt[] SEE ALSO -------- linkndctl:ndctl-update-firmware[1], https://pmem.io/documents/IntelOptanePMem_DSM_Interface-V2.0.pdf[Intel Optane PMem DSM Interface] ndctl-81/Documentation/ndctl/ndctl-check-labels.txt000066400000000000000000000012051476737544500225310ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 ndctl-check-labels(1) ==================== NAME ---- ndctl-check-labels - determine if the given dimms have a valid namespace index block SYNOPSIS -------- [verse] 'ndctl check-labels' [..] [] include::labels-description.txt[] In addition to checking if a label area has a valid index block, running this command in verbose mode reports the reason the index block is deemed invalid. OPTIONS ------- include::labels-options.txt[] include::../copyright.txt[] SEE ALSO -------- http://www.uefi.org/sites/default/files/resources/UEFI_Spec_2_7.pdf[UEFI NVDIMM Label Protocol] ndctl-81/Documentation/ndctl/ndctl-check-namespace.txt000066400000000000000000000041611476737544500232270ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 ndctl-check-namespace(1) ========================= NAME ---- ndctl-check-namespace - check namespace metadata consistency SYNOPSIS -------- [verse] 'ndctl check-namespace' [] DESCRIPTION ----------- A namespace in the 'sector' mode will have metadata on it to describe the kernel BTT (Block Translation Table). The check-namespace command can be used to check the consistency of this metadata, and optionally, also attempt to repair it, if it has enough information to do so. The namespace being checked has to be disabled before initiating a check on it as a precautionary measure. The --force option can override this. EXAMPLES -------- Check a namespace (only report errors) [verse] ndctl disable-namespace namespace0.0 ndctl check-namespace namespace0.0 Check a namespace, and perform repairs if possible [verse] ndctl disable-namespace namespace0.0 ndctl check-namespace --repair namespace0.0 OPTIONS ------- -R:: --repair:: Perform metadata repairs if possible. Without this option, the raw namespace contents will not be touched. -L:: --rewrite-log:: Regenerate the BTT log and write it to media. This can be used to convert from the old (pre 4.15) padding format that was incompatible with other BTT implementations to the updated format. This requires the --repair option to be provided. WARNING: Do not interrupt this operation as it can potentially cause unrecoverable metadata corruption. It is highly recommended to create a backup of the raw namespace before attempting this. -f:: --force:: Unless this option is specified, a check-namespace operation will fail if the namespace is presently active. Specifying --force causes the namespace to be disabled before checking. -v:: --verbose:: Emit debug messages for the namespace check process. -r:: --region=:: include::xable-region-options.txt[] -b:: --bus=:: include::xable-bus-options.txt[] include::../copyright.txt[] SEE ALSO -------- linkndctl:ndctl-disable-namespace[1], linkndctl:ndctl-enable-namespace[1], http://www.uefi.org/sites/default/files/resources/UEFI_Spec_2_7.pdf[UEFI NVDIMM Label Protocol] ndctl-81/Documentation/ndctl/ndctl-clear-errors.txt000066400000000000000000000076161476737544500226300ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 ndctl-clear-errors(1) ===================== NAME ---- ndctl-clear-errors - clear all errors (badblocks) on the given namespace SYNOPSIS -------- [verse] 'ndctl clear-errors' [] DESCRIPTION ----------- A namespace may have one or more 'media errors', either known to the kernel or in a latent state. These error locations, or 'badblocks' can cause poison consumption events if read in an unsafe manner. Moreover, these badblocks also indicate that due to media corruption, any data that may have been in these locations has been unrecoverably lost. Normally, in the presence of such errors, the administrator is expected to recover the data from out of band means (such as backups), destroy the namespace, recreate it, and then restore the data. When the data is re-written, the writes will allow any errors to be cleared as they are encountered. In such a workflow, one should *never* need to use the 'clear-errors' command. However, there may be special use cases, where the data currently on the namespace does not matter - for example, if a 'devdax' mode namespace is being prepared for use as 'system-ram'. In such cases, it may be desirable to clear any errors on the namespace prior to switching its mode to prevent disruptive machine checks due to poison consumption. NOTE: *Only* use this command when the data on the namespace is immaterial. For any blocks that are cleared via this command, any data on the blocks in question will be lost, and replaced with content that is implementation (platform) defined, and unpredictable. WARNING: This is a DANGEROUS command, and should only be used after fully understanding its implications and consequences. This WILL erase your data. For namespaces in one of 'fsdax' or 'devdax' modes, this command will only consider the 'data' area for error clearing. Namespace metadata, such as info-blocks, will not be touched. For namespaces in 'raw' mode, the full available capacity of the namespace is considered for error clearing. Namespaces that are in 'sector' mode are not supported, and will be skipped. NOTE: It is expected that the command is run with the namespace 'enabled'. A namespace in the 'disabled' state will appear as, and will be treated as a 'raw' namespace, and error clearing will be performed for the full available capacity of the namespace, including any potential metadata areas. If there happen to be errors in the metadata area, clearing them may result in unpredictable outcomes. You have been warned! Known errors are ones that the kernel has encountered before, either via a previous scrub, or by an attempted read from those locations. These can be listed by running 'ndctl list --media-errors' for a given namespace. Latent errors, as the name indicates, are unknown to the kernel. These can be found by running a scrub operation on the NVDIMMs in question. By default, the ndctl-clear-errors command only clears known errors. This can be overridden using the '--scrub' option to clear *all* errors. NOTE: If a scrub is in progress when the command is called, it will unconditionally wait for it to complete. EXAMPLES -------- Clear errors on namespace 0.0 [verse] ndctl clear-errors namespace0.0 Clear errors on all namespaces belonging to region1, including scrubbing for latent errors [verse] ndctl clear-errors --scrub --region=region1 all OPTIONS ------- -s:: --scrub:: Perform a 'scrub' on the bus prior to clearing errors. This allows for the clearing of any latent media errors in addition to errors the kernel already knows about. NOTE: This will cause the command to start and wait for a full scrub, and this can potentially be a very long-running operation. -v:: --verbose:: Emit debug messages. -r:: --region=:: include::xable-region-options.txt[] -b:: --bus=:: include::xable-bus-options.txt[] include::../copyright.txt[] SEE ALSO -------- linkndctl:ndctl-start-scrub[1], linkndctl:ndctl-list[1] ndctl-81/Documentation/ndctl/ndctl-create-namespace.txt000066400000000000000000000222531476737544500234170ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 ndctl-create-namespace(1) ========================= NAME ---- ndctl-create-namespace - provision or reconfigure a namespace SYNOPSIS -------- [verse] 'ndctl create-namespace' [] include::namespace-description.txt[] EXAMPLES -------- Create a maximally sized pmem namespace in 'fsdax' mode (the default) [verse] ndctl create-namespace Convert namespace0.0 to 'sector' mode [verse] ndctl create-namespace -f -e namespace0.0 --mode=sector OPTIONS ------- -m:: --mode=:: - "raw": expose the namespace capacity directly with limitations. A raw pmem namepace namespace does not support sector atomicity (see "sector" mode below). A raw pmem namespace may have limited to no dax support depending the kernel. In other words operations like direct-I/O targeting a dax buffer may fail for a pmem namespace in raw mode or indirect through a page-cache buffer. See "fsdax" and "devdax" mode for dax operation. - "sector": persistent memory, given that it is byte addressable, does not support sector atomicity. The problematic aspect of sector tearing is that most applications do not know they have a atomic sector update dependency. At least a disk rarely ever tears sectors and if it does it almost certainly returns a checksum error on access. Persistent memory devices will always tear and always silently. Until an application is audited to be robust in the presence of sector-tearing "safe" mode is recommended. This imposes some performance overhead and disables the dax capability. (also known as "safe" or "btt" mode) - "fsdax": A pmem namespace in this mode supports dax operation with a block-device based filesystem (in previous ndctl releases this mode was named "memory" mode). This mode comes at the cost of allocating per-page metadata. The capacity can be allocated from "System RAM", or from a reserved portion of "Persistent Memory" (see the --map= option). NOTE: A filesystem that supports DAX is required for dax operation. If the raw block device (/dev/pmemX) is used directly without a filesystem, it will use the page cache. See "devdax" mode for raw device access that supports dax. - "devdax": The device-dax character device interface is a statically allocated / raw access analogue of filesystem-dax (in previous ndctl releases this mode was named "dax" mode). It allows memory ranges to be mapped without need of an intervening filesystem. The device-dax is interface strict, precise and predictable. Specifically the interface: * Guarantees fault granularity with respect to a given page size (4K, 2M, or 1G on x86) set at configuration time. * Enforces deterministic behavior by being strict about what fault scenarios are supported. I.e. if a device is configured with a 2M alignment an attempt to fault a 4K aligned offset will result in SIGBUS. :: Note both 'fsdax' and 'devdax' mode require 16MiB physical alignment to be cross-arch compatible. By default ndctl will block attempts to create namespaces in these modes when the physical starting address of the namespace is not 16MiB aligned. The --force option tries to override this constraint if the platform supports a smaller alignment, but this is not recommended. -s:: --size=:: For NVDIMM devices that support namespace labels, set the namespace size in bytes. Otherwise it defaults to the maximum size specified by platform firmware. This option supports the suffixes "k" or "K" for KiB, "m" or "M" for MiB, "g" or "G" for GiB and "t" or "T" for TiB. For pmem namepsaces the size must be a multiple of the interleave-width and the namespace alignment (see below). -a:: --align:: Applications that want to establish dax memory mappings with page table entries greater than system base page size (4K on x86) need a persistent memory namespace that is sufficiently aligned. For "fsdax" and "devdax" mode this defaults to 2M. Note that "devdax" mode enforces all mappings to be aligned to this value, i.e. it fails unaligned mapping attempts. The "fsdax" alignment setting determines the starting alignment of filesystem extents and may limit the possible granularities, if a large mapping is not possible it will silently fall back to a smaller page size. -e:: --reconfig=:: Reconfigure an existing namespace. This option is a shortcut for the following sequence: - Read all parameters from @victim_namespace - Destroy @victim_namespace - Create @new_namespace merging old parameters with new ones :: Note that the major implication of a destroy-create cycle is that data from @victim_namespace is not preserved in @new_namespace. The attributes transferred from @victim_namespace are the geometry, mode, and name (not uuid without --uuid=). No attempt is made to preserve the data and any old data that is visible in @new_namespace is by coincidence not convention. "Backup and restore" is the only reliable method to populate @new_namespace with data from @victim_namespace. -u:: --uuid=:: This option is not recommended as a new uuid should be generated every time a namespace is (re-)created. For recovery scenarios however the uuid may be specified. -n:: --name=:: For NVDIMM devices that support namespace labels, specify a human friendly name for a namespace. This name is available as a device attribute for use in udev rules. -l:: --sector-size:: Specify the logical sector size (LBA size) of the Linux block device associated with an namespace. -M:: --map=:: A pmem namespace in "fsdax" or "devdax" mode requires allocation of per-page metadata. The allocation can be drawn from either: - "mem": typical system memory - "dev": persistent memory reserved from the namespace :: Given relative capacities of "Persistent Memory" to "System RAM" the allocation defaults to reserving space out of the namespace directly ("--map=dev"). The overhead is 64-bytes per 4K (16GB per 1TB) on x86. -c:: --continue:: Do not stop after creating one namespace. Instead, greedily create as many namespaces as possible within the given --bus and --region filter restrictions. This will abort if any creation attempt results in an error unless --force is also supplied. -f:: --force:: Unless this option is specified the 'reconfigure namespace' operation will fail if the namespace is presently active. Specifying --force causes the namespace to be disabled before the operation is attempted. However, if the namespace is mounted then the 'disable namespace' and 'reconfigure namespace' operations will be aborted. The namespace must be unmounted before being reconfigured. When used in conjunction with --continue, continue the namespace creation loop even if an error is encountered for intermediate namespaces. -L:: --autolabel:: --no-autolabel:: Legacy NVDIMM devices do not support namespace labels. In that case the kernel creates region-sized namespaces that can not be deleted. Their mode can be changed, but they can not be resized smaller than their parent region. This is termed a "label-less namespace". In contrast, NVDIMMs and hypervisors that support the ACPI 6.2 label area definition (ACPI 6.2 Section 6.5.10 NVDIMM Label Methods) support "labelled namespace" operation. - There are two cases where the kernel will default to label-less operation: * NVDIMM does not support labels * The NVDIMM supports labels, but the Label Index Block (see UEFI 2.7) is not present. - In the latter case the configuration can be upgraded to labelled operation by writing an index block on all DIMMs in a region and re-enabling that region. The 'autolabel' capability of 'ndctl create-namespace --reconfig' tries to do this by default if it can determine that all DIMM capacity is referenced by the namespace being reconfigured. It will otherwise fail to autolabel and remain in label-less mode if it finds a DIMM contributes capacity to more than one region. This check prevents inadvertent data loss of that other region is in active use. The --autolabel option is implied by default, the --no-autolabel option can be used to disable this behavior. When automatic labeling fails and labelled operation is still desired the safety policy can be bypassed by the following commands, note that all data on all regions is forfeited by running these commands: ndctl disable-region all ndctl init-labels all ndctl enable-region all -R:: --autorecover:: --no-autorecover:: By default, if a namespace creation attempt fails, ndctl will cleanup the partially initialized namespace. Use --no-autorecover to disable this behavior for debug and development scenarios where it useful to have the label and info-block state preserved after a failure. -v:: --verbose:: Emit debug messages for the namespace creation process -r:: --region=:: include::xable-region-options.txt[] -b:: --bus=:: include::xable-bus-options.txt[] include::../copyright.txt[] SEE ALSO -------- linkndctl:ndctl-zero-labels[1], linkndctl:ndctl-init-labels[1], linkndctl:ndctl-disable-namespace[1], linkndctl:ndctl-enable-namespace[1], http://www.uefi.org/sites/default/files/resources/UEFI_Spec_2_7.pdf[UEFI NVDIMM Label Protocol] https://nvdimm.wiki.kernel.org[Linux Persistent Memory Wiki] ndctl-81/Documentation/ndctl/ndctl-destroy-namespace.txt000066400000000000000000000014561476737544500236470ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 ndctl-destroy-namespace(1) ========================= NAME ---- ndctl-destroy-namespace - destroy the given namespace(s) SYNOPSIS -------- [verse] 'ndctl destroy-namespace' [] include::namespace-description.txt[] OPTIONS ------- include::xable-namespace-options.txt[] -f:: --force:: Unless this option is specified the 'destroy namespace' operation will fail if the namespace is presently active. Specifying --force causes the namespace to be disabled before the operation is attempted. However, if the namespace is mounted then the 'disable namespace' and 'destroy namespace' operations will be aborted. The namespace must be unmounted before being destroyed. include::../copyright.txt[] SEE ALSO -------- linkndctl:ndctl-create-namespace[1] ndctl-81/Documentation/ndctl/ndctl-disable-dimm.txt000066400000000000000000000006571476737544500225550ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 ndctl-disable-dimm(1) ===================== NAME ---- ndctl-disable-dimm - disable one or more idle dimms SYNOPSIS -------- [verse] 'ndctl disable-dimm' [] include::dimm-description.txt[] OPTIONS ------- :: include::xable-dimm-options.txt[] -b:: --bus=:: include::xable-bus-options.txt[] include::../copyright.txt[] SEE ALSO -------- linkndctl:ndctl-enable-dimm[1] ndctl-81/Documentation/ndctl/ndctl-disable-namespace.txt000066400000000000000000000006351476737544500235570ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 ndctl-disable-namespace(1) ========================= NAME ---- ndctl-disable-namespace - disable the given namespace(s) SYNOPSIS -------- [verse] 'ndctl disable-namespace' [] include::namespace-description.txt[] OPTIONS ------- include::xable-namespace-options.txt[] include::../copyright.txt[] SEE ALSO -------- linkndctl:ndctl-enable-namespace[1] ndctl-81/Documentation/ndctl/ndctl-disable-region.txt000066400000000000000000000007341476737544500231060ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 ndctl-disable-region(1) ======================= NAME ---- ndctl-disable-region - disable the given region(s) and all descendant namespaces SYNOPSIS -------- [verse] 'ndctl disable-region' [] include::region-description.txt[] OPTIONS ------- :: include::xable-region-options.txt[] -b:: --bus=:: include::xable-bus-options.txt[] include::../copyright.txt[] SEE ALSO -------- linkndctl:ndctl-enable-region[1] ndctl-81/Documentation/ndctl/ndctl-enable-dimm.txt000066400000000000000000000006431476737544500223730ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 ndctl-enable-dimm(1) ==================== NAME ---- ndctl-enable-dimm - enable one more dimms SYNOPSIS -------- [verse] 'ndctl enable-dimm' [] include::dimm-description.txt[] OPTIONS ------- :: include::xable-dimm-options.txt[] -b:: --bus=:: include::xable-bus-options.txt[] include::../copyright.txt[] SEE ALSO -------- linkndctl:ndctl-disable-dimm[1] ndctl-81/Documentation/ndctl/ndctl-enable-namespace.txt000066400000000000000000000006321476737544500233770ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 ndctl-enable-namespace(1) ========================= NAME ---- ndctl-enable-namespace - enable the given namespace(s) SYNOPSIS -------- [verse] 'ndctl enable-namespace' [] include::namespace-description.txt[] OPTIONS ------- include::xable-namespace-options.txt[] include::../copyright.txt[] SEE ALSO -------- linkndctl:ndctl-disable-namespace[1] ndctl-81/Documentation/ndctl/ndctl-enable-region.txt000066400000000000000000000007301476737544500227250ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 ndctl-enable-region(1) ====================== NAME ---- ndctl-enable-region - enable the given region(s) and all descendant namespaces SYNOPSIS -------- [verse] 'ndctl enable-region' [] include::region-description.txt[] OPTIONS ------- :: include::xable-region-options.txt[] -b:: --bus=:: include::xable-bus-options.txt[] include::../copyright.txt[] SEE ALSO -------- linkndctl:ndctl-disable-region[1] ndctl-81/Documentation/ndctl/ndctl-freeze-security.txt000066400000000000000000000022271476737544500233460ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 ndctl-freeze-security(1) ======================== NAME ---- ndctl-freeze-security - Set the given DIMM(s) to reject future security operations SYNOPSIS -------- [verse] 'ndctl freeze-security' [..] [] DESCRIPTION ----------- Prevent any further security operations on the given DIMMs until the next reboot. This is used in scenarios where the administrator has taken all expected security actions for the current boot and wants the DIMM to enforce / lock the current state. EXAMPLES -------- ---- $ ndctl list -d nmem0 [ { "dev":"nmem0", "id":"cdab-0a-07e0-ffffffff", "handle":0, "phys_id":0, "security":"unlocked" } ] $ ndctl freeze-security nmem0 security froze 1 nmem. $ ndctl list -d nmem0 [ { "dev":"nmem0", "id":"cdab-0a-07e0-ffffffff", "handle":0, "phys_id":0, "security":"unlocked", "security_frozen":true }, ] ---- OPTIONS ------- :: include::xable-dimm-options.txt[] -b:: --bus=:: include::xable-bus-options.txt[] -v:: --verbose:: Emit debug messages. include::intel-nvdimm-security.txt[] include::../copyright.txt[] ndctl-81/Documentation/ndctl/ndctl-init-labels.txt000066400000000000000000000036541476737544500224310ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 ndctl-init-labels(1) ==================== NAME ---- ndctl-init-labels - initialize the label data area on a dimm or set of dimms SYNOPSIS -------- [verse] 'ndctl init-labels' [..] [] include::labels-description.txt[] Starting with v4.10 the kernel will honor labels for sub-dividing PMEM if all the DIMMs in an interleave set / region have a valid namespace index block. This command can be used to initialize the namespace index block if it is missing or reinitialize it if it is damaged. Note that reinitialization effectively destroys all existing namespace labels on the DIMM. EXAMPLE ------- Find the DIMMs that comprise a given region: ---- # ndctl list -RD --region=region1 { "dimms":[ { "dev":"nmem0", "id":"8680-56341200" } ], "regions":[ { "dev":"region1", "size":268435456, "available_size":0, "type":"pmem", "mappings":[ { "dimm":"nmem0", "offset":13958643712, "length":268435456 } ] } ] } ---- Disable that region so the DIMM label area can be written from userspace: ---- # ndctl disable-region region1 ---- Initialize labels: ---- # ndctl init-labels nmem0 ---- Re-enable the region: ---- # ndctl enable-region region1 ---- Create a namespace in that region: ---- # ndctl create-namespace --region=region1 ---- OPTIONS ------- include::labels-options.txt[] -f:: --force:: Force initialization of the label space even if there appears to be an existing / valid namespace index. Warning, this will destroy all defined namespaces on the dimm. -V:: --label-version:: Initialize with a specific version of labels from the namespace label specification. Defaults to 1.1 include::../copyright.txt[] SEE ALSO -------- linkndctl:ndctl-create-namespace[1], http://www.uefi.org/sites/default/files/resources/UEFI_Spec_2_7.pdf[UEFI NVDIMM Label Protocol] ndctl-81/Documentation/ndctl/ndctl-inject-error.txt000066400000000000000000000077401476737544500226310ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 ndctl-inject-error(1) ===================== NAME ---- ndctl-inject-error - inject media errors at a namespace offset SYNOPSIS -------- [verse] 'ndctl inject-error' [] include::namespace-description.txt[] ndctl-inject-error can be used to ask the platform to simulate media errors in the NVDIMM address space to aid debugging and development of features related to error handling. By default, injecting an error actually only injects an error to the first 'n' bytes of the block, where 'n' is the output of ndctl_cmd_ars_cap_get_size(). In other words, we only inject one 'ars_unit' per sector. This is sufficient for Linux to mark the whole sector as bad, and will show up as such in the various 'badblocks' lists in the kernel. If multiple blocks are being injected, only the first 'n' bytes of each block specified will be injected as errors. This can be overridden by the --saturate option, which will force the entire block to be injected as an error. WARNING: These commands are DANGEROUS and can cause data loss. They are only provided for testing and debugging purposes. EXAMPLES -------- Inject errors in namespace0.0 at block 12 for 2 blocks (i.e. 12, 13) [verse] ndctl inject-error --block=12 --count=2 namespace0.0 Check status of injected errors on namespace0.0 [verse] ndctl inject-error --status namespace0.0 Uninject errors at block 12 for 2 blocks on namespace0.0 [verse] ndctl inject-error --uninject --block=12 --count=2 namespace0.0 OPTIONS ------- -B:: --block=:: Namespace block offset in 512 byte sized blocks where the error is to be injected. NOTE: The offset is interpreted in different ways based on the "mode" of the namespace. For "raw" mode, the offset is the base namespace offset. For "fsdax" mode (i.e. a "pfn" namespace), the offset is relative to the user-visible part of the namespace, and the offset introduced by the kernel's metadata will be accounted for. For a "sector" mode namespace (i.e. a "BTT" namespace), the offset is relative to the base namespace, as the BTT translation details are internal to the kernel, and can't be accounted for while injecting errors. -n:: --count=:: Number of blocks to inject as errors. This is also in terms of fixed, 512 byte blocks. -d:: --uninject:: This option will ask the platform to remove any injected errors for the specified block offset, and count. WARNING: This will not clear the kernel's internal badblock tracking, those can only be cleared by doing a write to the affected locations. Hence use the --clear option only if you know exactly what you are doing. For normal usage, injected errors should only be cleared by doing writes. Do not expect have the original data intact after injecting an error, and clearing it using --clear - it will be lost, as the only "real" way to clear the error location is to write to it or zero it (truncate/hole-punch). -t:: --status:: This option will retrieve the status of injected errors. Note that this will not retrieve all known/latent errors (i.e. non injected ones), and is NOT equivalent to performing an Address Range Scrub. -N:: --no-notify:: This option is only valid when injecting errors. By default, the error inject command and will ask platform firmware to trigger a notification in the kernel, asking it to update its state of known errors. With this option, the error will still be injected, the kernel will not get a notification, and the error will appear as a latent media error when the location is accessed. If the platform firmware does not support this feature, this will have no effect. -S:: --saturate:: This option forces error injection or un-injection to cover the entire address range covered by the specified block(s). -v:: --verbose:: Emit debug messages for the error injection process include::human-option.txt[] -r:: --region=:: include::xable-region-options.txt[] -b:: --bus=:: include::xable-bus-options.txt[] include::../copyright.txt[] SEE ALSO -------- linkndctl:ndctl-list[1], ndctl-81/Documentation/ndctl/ndctl-inject-smart.txt000066400000000000000000000051241476737544500226200ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 ndctl-inject-smart(1) ===================== NAME ---- ndctl-inject-smart - perform smart threshold/injection operations on a DIMM SYNOPSIS -------- [verse] 'ndctl inject-smart' [] DESCRIPTION ----------- A generic DIMM device object, named /dev/nmemX, is registered for each memory device indicated in the ACPI NFIT table, or other platform NVDIMM resource discovery mechanism. ndctl-inject-smart can be used to set smart thresholds, and inject smart attributes. EXAMPLES -------- Set smart controller temperature and spares threshold for DIMM-0 to 32C, spares threshold to 8, and enable the spares alarm. [verse] ndctl inject-smart --ctrl-temperature-threshold=32 --spares-threshold=8 --spares-alarm nmem0 Inject a media temperature value of 52 and fatal health status flag for DIMM-0 [verse] ndctl inject-smart --media-temperature=52 --health=fatal nmem0 OPTIONS ------- -b:: --bus=:: include::xable-bus-options.txt[] -m:: --media-temperature=:: Inject for the media temperature smart attribute. -M:: --media-temperature-threshold=:: Set for the smart media temperature threshold. --media-temperature-alarm=:: Enable or disable the smart media temperature alarm. Options are 'on' or 'off'. --media-temperature-uninject:: Uninject any media temperature previously injected. -c:: --ctrl-temperature=:: Inject for the controller temperature smart attribute. -C:: --ctrl-temperature-threshold=:: Set for the smart controller temperature threshold. --ctrl-temperature-alarm=:: Enable or disable the smart controller temperature alarm. Options are 'on' or 'off'. --ctrl-temperature-uninject:: Uninject any controller temperature previously injected. -s:: --spares=:: Inject for the spares smart attribute. -S:: --spares-threshold=:: Set for the smart spares threshold. --spares-alarm=:: Enable or disable the smart spares alarm. Options are 'on' or 'off'. --spares-uninject:: Uninject any spare percentage previously injected. -f:: --fatal:: Set the flag to spoof fatal health status. --fatal-uninject:: Uninject the fatal health status flag. -U:: --unsafe-shutdown:: Set the flag to spoof an unsafe shutdown on the next power down. --unsafe-shutdown-uninject:: Uninject the unsafe shutdown flag. -N:: --uninject-all:: Uninject all possible smart fields/values irrespective of whether they have been previously injected or not. -v:: --verbose:: Emit debug messages for the error injection process include::human-option.txt[] include::../copyright.txt[] SEE ALSO -------- linkndctl:ndctl-list[1], ndctl-81/Documentation/ndctl/ndctl-list.txt000066400000000000000000000156251476737544500212020ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 ndctl-list(1) ============= NAME ---- ndctl-list - dump the platform nvdimm device topology and attributes in json SYNOPSIS -------- [verse] 'ndctl list' [] Walk all the nvdimm buses in the system and list all attached devices along with some of their major attributes. Options can be specified to limit the output to devices of a certain class. Where the classes are buses, dimms, regions, and namespaces. By default, 'ndctl list' with no options is equivalent to: [verse] ndctl list --namespaces --bus=all --region=all EXAMPLE ------- ---- # ndctl list --buses --namespaces { "provider":"nfit_test.1", "dev":"ndbus2", "namespaces":[ { "dev":"namespace9.0", "mode":"raw", "size":33554432, "blockdev":"pmem9" } ] } { "provider":"nfit_test.0", "dev":"ndbus1" } { "provider":"e820", "dev":"ndbus0", "namespaces":[ { "dev":"namespace0.0", "mode":"fsdax", "size":8589934592, "blockdev":"pmem0" } ] } ---- OPTIONS ------- -r:: --region=:: include::xable-region-options.txt[] -b:: --bus=:: include::xable-bus-options.txt[] -d:: --dimm=:: An 'nmemX' device name, or dimm id number. The dimm id number here is X in 'nmemX'. Filter listing by devices that reference the given dimm. For example to see all namespaces comprised of storage capacity on nmem0: ---- # ndctl list --dimm=nmem0 --namespaces ---- -n:: --namespace=:: An 'namespaceX.Y' device name, or namespace region plus id tuple 'X.Y'. Limit the namespace list to the single identified device if present. -m:: --mode=:: Filter listing by the mode ('raw', 'fsdax', 'sector' or 'devdax') of the namespace(s). -U:: --numa-node=:: Filter listing by numa node -B:: --buses:: Include bus info in the listing -D:: --dimms:: Include dimm info in the listing [verse] { "dev":"nmem0", "id":"cdab-0a-07e0-ffffffff", "handle":0, "phys_id":0, "security:":"disabled" } -H:: --health:: Include dimm health info in the listing. For example: [verse] { "dev":"nmem0", "health":{ "health_state":"non-critical", "temperature_celsius":23, "spares_percentage":75, "alarm_temperature":true, "alarm_spares":true, "temperature_threshold":40, "spares_threshold":5, "life_used_percentage":5, "shutdown_state":"clean" } } -F:: --firmware:: Include firmware info in the listing, including the state and capability of runtime firmware activation: ---- # ndctl list -BDF [ { "provider":"nfit_test.0", "dev":"ndbus2", "scrub_state":"idle", "firmware":{ "activate_method":"suspend", "activate_state":"idle" }, "dimms":[ { "dev":"nmem1", "id":"cdab-0a-07e0-ffffffff", "handle":0, "phys_id":0, "security":"disabled", "firmware":{ "current_version":0, "can_update":true } }, ... ] ---- -X:: --device-dax:: Include device-dax ("daxregion") details when a namespace is in "devdax" mode. [verse] { "dev":"namespace0.0", "mode":"devdax", "size":4225761280, "uuid":"18ae1bbb-bb62-4efc-86df-4a5caacb5dcc", "daxregion":{ "id":0, "size":4225761280, "align":2097152, "devices":[ { "chardev":"dax0.0", "size":4225761280 } ] } } -R:: --regions:: Include region info in the listing -N:: --namespaces:: Include namespace info in the listing. Namespace info is listed by default if no other options are specified to the command. -i:: --idle:: Include idle (not enabled) devices in the listing -c:: --configured:: Include configured devices (non-zero sized namespaces) regardless of whether they are enabled, or not. Other devices besides namespaces are always considered "configured". -C:: --capabilities:: Include region capabilities in the listing, i.e. supported namespace modes and variable properties like sector sizes and alignments. -M:: --media-errors:: Include media errors (badblocks) in the listing. Note that the 'badblock_count' property is included in the listing by default when the count is non-zero, otherwise it is hidden. Also, if the namespace is in 'sector' mode the 'badblocks' listing is not included and 'badblock_count' property may include blocks that are located in metadata, or unused capacity in the namespace. Convert a 'sector' namespace into 'raw' mode to list precise 'badblocks' offsets. [verse] { "dev":"namespace7.0", "mode":"raw", "size":33554432, "blockdev":"pmem7", "badblock_count":17, "badblocks":[ { "offset":4, "length":1 }, { "offset":32768, "length":8 }, { "offset":65528, "length":8 } ] } -v:: --verbose:: Increase verbosity of the output. This can be specified multiple times to be even more verbose on the informational and miscellaneous output, and can be used to override omitted flags for showing specific information. + - *-v* In addition to the enabled namespaces default output, show the numa_node, raw_uuid, and bad block media errors. + - *-vv* Everything '-v' provides, plus automatically enable --dimms, --buses, and --regions. + - *-vvv* Everything '-vv' provides, plus --health, --capabilities, --idle, and --firmware. :: The verbosity can also be scoped by the object type. For example to just list regions with capabilities and media error info. ---- # ndctl list -Ru -vvv -r 0 { "dev":"region0", "size":"4.00 GiB (4.29 GB)", "available_size":0, "max_available_extent":0, "type":"pmem", "numa_node":0, "target_node":2, "capabilities":[ { "mode":"sector", "sector_sizes":[ 512, 520, 528, 4096, 4104, 4160, 4224 ] }, { "mode":"fsdax", "alignments":[ 4096, 2097152, 1073741824 ] }, { "mode":"devdax", "alignments":[ 4096, 2097152, 1073741824 ] } ], "persistence_domain":"unknown" } ---- include::human-option.txt[] ---- # ndctl list --region=7 { "dev":"region7", "size":67108864, "available_size":67108864, "type":"pmem", "iset_id":-6382611090938810793, "badblock_count":8 } ---- ---- # ndctl list --human --region=7 { "dev":"region7", "size":"64.00 MiB (67.11 MB)", "available_size":"64.00 MiB (67.11 MB)", "type":"pmem", "iset_id":"0xa76c6907811fae57", "badblock_count":8 } ---- ENVIRONMENT VARIABLES --------------------- 'NDCTL_LIST_LINT':: A bug in the "ndctl list" output needs to be fixed with care for other tooling that may have developed a dependency on the buggy behavior. The NDCTL_LIST_LINT variable is an opt-in to apply fixes, and not regress previously shipped behavior by default. This environment variable applies the following fixups: - Fix "ndctl list -Rv" to only show region objects and not include namespace objects. :: include::../copyright.txt[] SEE ALSO -------- linkndctl:ndctl-create-namespace[1] ndctl-81/Documentation/ndctl/ndctl-load-keys.txt000066400000000000000000000030031476737544500221020ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 ndctl-load-keys(1) ================== NAME ---- ndctl-load-keys - load the kek and encrypted passphrases into the keyring SYNOPSIS -------- [verse] 'ndctl load-keys' [] DESCRIPTION ----------- The 'load-keys' command loads the master key ('kek') and the encrypted passphrases for all NVDIMMs into the user keyring maintained by the kernel. The command is expected to be called during initialization and before the libnvdimm kernel module is loaded, typically from an initrd. This is typically set up using a modprobe config that calls the command before module load. NOTE: All key files are expected to be in the format: nvdimm__hostname + The `'_`' character is used to delimit the different components in the file name. Within the hostname, the `'_`' character is allowed since it is the last component of the file name. NOTE: This command is typically never called directly by a user. OPTIONS ------- -p:: --key-path=:: Path to where key related files reside. This parameter is optional and the default location is {ndctl_keysdir}. -t:: --tpm-handle=:: Provide a TPM handle (should be a string such as 0x81000001). If the key path ({ndctl_keysdir}) contains a file called tpm.handle which contains the handle string, then this option may be left out, and the tpm handle will be obtained from the file. If both are present, then this option will override (but not overwrite) anything that is in the file. include::intel-nvdimm-security.txt[] include::../copyright.txt[] ndctl-81/Documentation/ndctl/ndctl-monitor.txt000066400000000000000000000063151476737544500217120ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 ndctl-monitor(1) ================ NAME ---- ndctl-monitor - Monitor the smart events of nvdimm objects SYNOPSIS -------- [verse] 'ndctl monitor' [] DESCRIPTION ----------- Ndctl monitor is used for monitoring the smart events of nvdimm objects and dumping the json format notifications to syslog, standard output or a logfile. The objects to monitor and smart events to notify can be selected by setting options and/or configuration files with .conf suffix under {ndctl_confdir} Both, the values in configuration file and in options will work. If there is a conflict, the values in options will override the values in the configuration file. Any updated values in the configuration file will take effect only after the monitor process is restarted. EXAMPLES -------- Run a monitor as a daemon to monitor DIMMs on bus "nfit_test.1" [verse] ndctl monitor --bus=nfit_test.1 --daemon Run a monitor as a one-shot command and output the notifications to /var/log/ndctl.log [verse] ndctl monitor --log=/var/log/ndctl.log Run a monitor daemon as a system service [verse] systemctl start ndctl-monitor.service OPTIONS ------- -b:: --bus=:: include::xable-bus-options.txt[] -d:: --dimm=:: include::xable-dimm-options.txt[] -r:: --region=:: include::xable-region-options.txt[] -n:: --namespace=:: A 'namespaceX.Y' device name, or namespace region plus id tuple 'X.Y'. -l:: --log=:: Send log messages to the specified destination. - "": Send log messages to specified . When fopen() is not able to open , log messages will be forwarded to syslog. - "syslog": Send messages to syslog. - "standard": Send messages to standard output. The default log destination is 'syslog' if "--daemon" is specified, otherwise 'standard'. Note that standard and relative path for will not work if "--daemon" is specified. -c:: --config-file=:: Provide the config file(s) to use. This overrides the default config typically found in {ndctl_confdir} --daemon:: Run a monitor as a daemon. -D:: --dimm-event=:: Name of an smart health event from the following: - "dimm-spares-remaining": Spare Blocks Remaining value has gone below the pre-programmed threshold. - "dimm-media-temperature": NVDIMM Media temperature value has gone above the pre-programmed threshold. - "dimm-controller-temperature": NVDIMM Controller temperature value has gone above the pre-programmed threshold. - "dimm-health-state": NVDIMM Normal Health Status has changed - "dimm-unclean-shutdown": NVDIMM Last Shutdown Status was a unclean shutdown. The monitor will attempt to enable the alarm control bits for all specified events. -p:: --poll=:: Poll and report status/event every seconds. -u:: --human:: Output monitor notification as human friendly json format instead of the default machine friendly json format. -v:: --verbose:: Emit extra debug messages to log. COPYRIGHT --------- Copyright (c) 2018, FUJITSU LIMITED. License GPLv2: GNU GPL version 2 . This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. SEE ALSO -------- linkndctl:ndctl-list[1], linkndctl:ndctl-inject-smart[1] ndctl-81/Documentation/ndctl/ndctl-read-infoblock.txt000066400000000000000000000046071476737544500231040ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 ndctl-read-infoblock(1) ======================= NAME ---- ndctl-read-infoblock - read and optionally parse the info-block a namespace SYNOPSIS -------- [verse] 'ndctl read-infoblock' [..] [] DESCRIPTION ----------- As described in the theory of operation section of linkndctl:ndctl-create-namespace[1], the raw capacity of a namespace may encapsulate a personality, or mode of operation. Specifically, the mode may be set to one of "sector", "fsdax", and "devdax". Each of those modes is defined by an info-block format that uniquely identifies the mode of operation. The read-infoblock command knows the location (relative to the start of the namespace) and field definition of those data structures. Note that unlike a partition table info-block is not exposed by default, so the namespace needs to be disabled before the info-block can be accessed. EXAMPLE ------- [verse] ndctl disable-namespace namespace0.0 disabled 1 namespace ndctl read-infoblock -j namespace0.0 [ { "dev":"namespace0.0", "signature":"NVDIMM_PFN_INFO", "uuid":"56b11990-66b1-4d91-9cac-ca084c051259", "parent_uuid":"00000000-0000-0000-0000-000000000000", "flags":0, "version":"1.3", "dataoff":69206016, "npfns":1031680, "mode":2, "start_pad":0, "end_trunc":0, "align":2097152 } ] OPTIONS ------- :: One or more 'namespaceX.Y' device names. The keyword 'all' can be specified to operate on every namespace in the system, optionally filtered by bus id (see --bus= option), or region id (see --region= option). -V:: --verify:: Attempt to validate that the info-block is self consistent by validating the embedded checksum, and that info-block formats that contain a 'parent-uuid' attribute also match the base-uuid of the namespace. -o:: --output:: Output file -j:: --json:: Parse the info-block data into json. -u:: --human:: Enable json output and convert number formats to human readable strings, for example show the size in terms of "KB", "MB", "GB", etc instead of a signed 64-bit numbers per the JSON interchange format (implies --json). -r:: --region=:: include::xable-region-options.txt[] include::../copyright.txt[] SEE ALSO -------- linkndctl:ndctl-create-namespace[1], http://www.uefi.org/sites/default/files/resources/UEFI_Spec_2_7.pdf[UEFI NVDIMM Label Protocol] ndctl-81/Documentation/ndctl/ndctl-read-labels.txt000066400000000000000000000022521476737544500223720ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 ndctl-read-labels(1) ==================== NAME ---- ndctl-read-labels - read out the label area on a dimm or set of dimms SYNOPSIS -------- [verse] 'ndctl read-labels' [..] [] include::labels-description.txt[] This command dumps the raw binary data in a dimm's label area to stdout or a file. In the multi-dimm case the data is concatenated. OPTIONS ------- include::labels-options.txt[] -I:: --index:: Limit the span of the label operation to just the index-block area. This is useful to determine if the dimm label area is initialized. Note that this option and --size/--offset are mutually exclusive. -o:: --output:: output file -j:: --json:: parse the label data into json assuming the 'NVDIMM Namespace Specification' format. -u:: --human:: enable json output and convert number formats to human readable strings, for example show the size in terms of "KB", "MB", "GB", etc instead of a signed 64-bit numbers per the JSON interchange format (implies --json). include::../copyright.txt[] SEE ALSO -------- http://www.uefi.org/sites/default/files/resources/UEFI_Spec_2_7.pdf[UEFI NVDIMM Label Protocol] ndctl-81/Documentation/ndctl/ndctl-remove-passphrase.txt000066400000000000000000000017651476737544500236730ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 ndctl-remove-passphrase(1) =========================== NAME ---- ndctl-remove-passphrase - Stop a DIMM from locking at power-loss and requiring a passphrase to access media SYNOPSIS -------- [verse] 'ndctl remove-passphrase' [..] [] DESCRIPTION ----------- Search the user keyring for an encrypted passphrase for the NVDIMM in question. If not found, attempt to load the passphrase blob. After disabling the passphrase, remove the 'key-ID' from the keyring as well as the passphrase blob from the file system. OPTIONS ------- :: include::xable-dimm-options.txt[] -b:: --bus=:: include::xable-bus-options.txt[] -v:: --verbose:: Emit debug messages. -m:: --master-passphrase:: Indicates that we are managing the master passphrase instead of the user passphrase. include::intel-nvdimm-security.txt[] include::../copyright.txt[] SEE ALSO: --------- linkndctl:ndctl-setup-passphrase[1], linkndctl:ndctl-update-passphrase[1] ndctl-81/Documentation/ndctl/ndctl-sanitize-dimm.txt000066400000000000000000000044231476737544500227730ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 ndctl-sanitize-dimm(1) ====================== NAME ---- ndctl-sanitize-dimm - Perform a cryptographic destruction or overwrite of the contents of the given NVDIMM(s) SYNOPSIS -------- [verse] 'ndctl sanitize-dimm' [..] [] DESCRIPTION ----------- The 'sanitize-dimm' command performs a cryptographic destruction of the contents of the given NVDIMM. It scrambles the data, and any metadata or info-blocks, but it doesn't modify namespace labels. Therefore, any namespaces on regions associated with the given NVDIMM will be retained, but they will end up in the 'raw' mode. Additionally, after completion of this command, the security and passphrase for the given NVDIMM will be disabled, and the passphrase and any key material will also be removed from the keyring and the ndctl keys directory at {ndctl_keysdir} The command supports two different methods of performing the cryptographic erase. The default is 'crypto-erase', but additionally, an 'overwrite' option is available which overwrites not only the data area, but also the label area, thus losing record of any namespaces the given NVDIMM participates in. OPTIONS ------- :: include::xable-dimm-options.txt[] -b:: --bus=:: include::xable-bus-options.txt[] -c:: --crypto-erase:: Replace the media encryption key on the NVDIMM causing all existing data to read as cipher text with the new key. This does not change label data. Namespaces get reverted to raw mode. -o:: --overwrite:: Wipe the entire DIMM, including label data. This can take significant time, and the command is non-blocking. With this option, the overwrite request is merely submitted to the NVDIMM, and the completion is asynchronous. Depending on the medium and capacity, overwrite may take tens of minutes to many hours. -m:: --master-passphrase:: Indicate that we are using the master passphrase to perform the erase. This only is applicable to the 'crypto-erase' option. -z:: --zero-key:: Passing in a key with payload that is just 0's. --verbose:: Emit debug messages. include::intel-nvdimm-security.txt[] include::../copyright.txt[] SEE ALSO -------- linkndctl:ndctl-wait-overwrite[1], https://trustedcomputinggroup.org/wp-content/uploads/TCG_SWG_SIIS_Version_1_07_Revision_1_00.pdf ndctl-81/Documentation/ndctl/ndctl-setup-passphrase.txt000066400000000000000000000031641476737544500235310ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 ndctl-setup-passphrase(1) ========================= NAME ---- ndctl-setup-passphrase - setup and enable the security passphrase for an NVDIMM SYNOPSIS -------- [verse] 'ndctl setup-passphrase' [..] -k [] DESCRIPTION ----------- Setup and enable a security passphrase for one or more NVDIMMs. For this command to succeed, it is expected that the master key has previously been loaded into the user keyring. More information on how this can be done can be found in the kernel documentation at: https://www.kernel.org/doc/html/latest/security/keys/trusted-encrypted.html The passphrase blobs are created in the {ndctl_keysdir} directory with a file name format of `"nvdimm__.blob"` The command will fail if the passphrase is already in the user keyring or if a passphrase blob already exists in {ndctl_keysdir}. OPTIONS ------- :: include::xable-dimm-options.txt[] -b:: --bus=:: include::xable-bus-options.txt[] -k:: --key_handle=:: Handle for the master 'kek' (key-encryption-key) that will be used for sealing the passphrase(s) for the given DIMM(s). The format is: `:` e.g. `trusted:nvdimm-master` + NOTE: The 'kek' is expected to have been loaded into the user keyring. -m:: --master-passphrase:: Indicates that we are managing the master passphrase instead of the user passphrase. -v:: --verbose:: Emit debug messages. include::intel-nvdimm-security.txt[] include::../copyright.txt[] SEE ALSO: --------- linkndctl:ndctl-update-passphrase[1], linkndctl:ndctl-remove-passphrase[1] ndctl-81/Documentation/ndctl/ndctl-start-scrub.txt000066400000000000000000000026531476737544500224750ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 ndctl-start-scrub(1) ==================== NAME ---- ndctl-start-scrub - start an Address Range Scrub (ARS) operation SYNOPSIS -------- [verse] 'ndctl start-scrub' [ ... ] [] include::ars-description.txt[] The kernel provides a sysfs file ('scrub') that when written with the string "1\n" initiates an ARS operation. The 'ndctl start-scrub' operation starts an ARS, across all specified buses, and the kernel in turn proceeds to scrub every persistent memory address region on the specified buses. EXAMPLE ------- Start a scrub on all nvdimm buses in the system. The json listing report only includes the buses that support ARS operations. ---- # ndctl start-scrub [ { "provider":"nfit_test.1", "dev":"ndbus3", "scrub_state":"active" }, { "provider":"nfit_test.0", "dev":"ndbus2", "scrub_state":"active" } ] ---- When specifying an individual bus, or if there is only one bus in the system, the command reports whether ARS support is available. ---- # ndctl start-scrub e820 error starting scrub: Operation not supported ---- OPTIONS ------- -v:: --verbose:: Emit debug messages for the ARS start process include::../copyright.txt[] SEE ALSO -------- linkndctl:ndctl-wait-scrub[1], http://www.uefi.org/sites/default/files/resources/ACPI%206_2_A_Sept29.pdf[ACPI 6.2 Specification Section 9.20.7.2 Address Range Scrubbing (ARS) Overview] ndctl-81/Documentation/ndctl/ndctl-update-firmware.txt000066400000000000000000000057251476737544500233230ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 ndctl-update-firmware(1) ======================== NAME ---- ndctl-update-firmware - update the firmware the given device SYNOPSIS -------- [verse] 'ndctl update-firmware' [] DESCRIPTION ----------- Provide a generic interface for updating NVDIMM firmware. The use of this depends on support for the NVDIMM "family" in libndctl, the kernel needs to enable that command set, and the device itself needs to implement the command. Use "ndctl list -DF" to interrogate if firmware update is enabled. For example: [verse] ndctl list -DFu -d nmem1 { "dev":"nmem1", "id":"cdab-0a-07e0-ffffffff", "handle":"0", "phys_id":"0", "security":"disabled", "firmware":{ "current_version":"0", "can_update":true } } OPTIONS ------- :: include::xable-dimm-options.txt[] -b:: --bus=:: include::xable-bus-options.txt[] -f:: --firmware:: firmware file used to perform the update -i:: --force:: Ignore in-progress Address Range Scrub and try to submit the firmware update, or ignore firmware activate arm overflows and force-arm devices. -A:: --arm:: Arm a device for firmware activation. This is enabled by default when a firmware image is specified. Specify --no-arm to disable this default. Otherwise, without a firmware image, this option can be used to manually arm a device for firmware activate. When a device transitions from unarmed to armed the platform recalculates the firmware activation time and compares it against the maximum platform supported time. If the activation time would exceed the platform maximum the arm attempt is aborted: [verse] ndctl update-firmware --arm --bus=nfit_test.0 all Error: update firmware: nmem4: arm aborted, tripped overflow [ { "dev":"nmem1", "id":"cdab-0a-07e0-ffffffff", "handle":"0", "phys_id":"0", "security":"disabled", "firmware":{ "current_version":"0", "can_update":true } }, { "dev":"nmem3", "id":"cdab-0a-07e0-fffeffff", "handle":"0x100", "phys_id":"0x2", "security":"disabled", "firmware":{ "current_version":"0", "can_update":true } }, { "dev":"nmem2", "id":"cdab-0a-07e0-feffffff", "handle":"0x1", "phys_id":"0x1", "security":"disabled", "firmware":{ "current_version":"0", "can_update":true } } ] updated 3 nmems. It is possible, but not recommended, to ignore timeout overflows with the --force option. At any point to view the 'armed' state of the bus do: [verse] ndctl list -BF -b nfit_test.0 [ { "provider":"nfit_test.0", "dev":"ndbus2", "scrub_state":"idle", "firmware":{ "activate_method":"suspend", "activate_state":"overflow" } } ] -D:: --disarm:: Disarm devices after uploading the firmware file, or manually disarm devices when a firmware image is not specified. --no-disarm is not accepted. -v:: --verbose:: Emit debug messages for the namespace check process. include::../copyright.txt[] ndctl-81/Documentation/ndctl/ndctl-update-passphrase.txt000066400000000000000000000026211476737544500236500ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 ndctl-update-passphrase(1) ========================== NAME ---- ndctl-update-passphrase - update the security passphrase for an NVDIMM SYNOPSIS -------- [verse] 'ndctl update-passphrase' [..] [] DESCRIPTION ----------- Update the security passphrase for one or more NVDIMMs. Prerequisites for command to succeed: . The 'kek' has been loaded into the kernel's user keyring. . setup-passphrase has successfully been executed on the NVDIMM. * Alternatively, the NVDIMM is unlocked. The updated key blobs will be created in the {ndctl_keysdir} directory with a file name format of "nvdimm__.blob". OPTIONS ------- :: include::xable-dimm-options.txt[] -b:: --bus=:: include::xable-bus-options.txt[] -k:: --key_handle=:: Handle for the master 'kek' (key-encryption-key) that will be used for sealing the passphrase(s) for the given DIMM(s). The format is: `:` e.g. `trusted:nvdimm-master` + NOTE: The 'kek' is expected to have been loaded into the user keyring. -m:: --master-passphrase:: Indicates that we are managing the master passphrase instead of the user passphrase. -v:: --verbose:: Emit debug messages. include::intel-nvdimm-security.txt[] include::../copyright.txt[] SEE ALSO: --------- linkndctl:ndctl-setup-passphrase[1], linkndctl:ndctl-remove-passphrase[1] ndctl-81/Documentation/ndctl/ndctl-wait-overwrite.txt000066400000000000000000000013301476737544500232030ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 ndctl-wait-overwrite(1) ======================= NAME ---- ndctl-wait-overwrite - wait for an overwrite operation to complete SYNOPSIS -------- [verse] 'ndctl wait-overwrite' [..] [] DESCRIPTION ----------- The kernel provides a POLL(2) capable sysfs file ('security') to indicate the state of overwrite. This command waits for a change in the state of this file across all specified dimms. OPTIONS ------- :: include::xable-dimm-options.txt[] -b:: --bus=:: include::xable-bus-options.txt[] -v:: --verbose:: Emit debug messages. include::intel-nvdimm-security.txt[] include::../copyright.txt[] SEE ALSO -------- linkndctl:ndctl-sanitize-dimm[1] ndctl-81/Documentation/ndctl/ndctl-wait-scrub.txt000066400000000000000000000030441476737544500222770ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 ndctl-wait-scrub(1) ==================== NAME ---- ndctl-wait-scrub - wait for an Address Range Scrub (ARS) operation to complete SYNOPSIS -------- [verse] 'ndctl wait-scrub' [ ... ] [] include::ars-description.txt[] The kernel provides a POLL(2) capable sysfs file ('scrub') to indicate the state of ARS. The 'scrub' file maintains a running count of ARS runs that have taken place. While a current run is in progress a '+' character is emitted along with the current count. The 'ndctl wait-scrub' operation waits for 'scrub', across all specified buses, to indicate not in-progress at least once. EXAMPLE ------- Wait for scrub on all nvdimm buses in the system. The json listing report at the end only includes the buses that support ARS operations. ---- # ndctl wait-scrub [ { "provider":"nfit_test.1", "dev":"ndbus3", "scrub_state":"idle" }, { "provider":"nfit_test.0", "dev":"ndbus2", "scrub_state":"idle" } ] ---- When specifying an individual bus, or if there is only one bus in the system, the command reports whether ARS support is available. ---- # ndctl wait-scrub e820 error waiting for scrub completion: Operation not supported ---- OPTIONS ------- -v:: --verbose:: Emit debug messages for the ARS wait process include::../copyright.txt[] SEE ALSO -------- linkndctl:ndctl-start-scrub[1], http://www.uefi.org/sites/default/files/resources/ACPI%206_2_A_Sept29.pdf[ACPI 6.2 Specification Section 9.20.7.2 Address Range Scrubbing (ARS) Overview] ndctl-81/Documentation/ndctl/ndctl-write-infoblock.txt000066400000000000000000000072661476737544500233270ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 ndctl-write-infoblock(1) ======================== NAME ---- ndctl-write-infoblock - generate and write an infoblock SYNOPSIS -------- [verse] 'ndctl write-infoblock' [ | -o | --stdout] [] DESCRIPTION ----------- As described in the theory of operation section of linkndctl:ndctl-create-namespace[1], the raw capacity of a namespace may encapsulate a personality, or mode of operation. Specifically, the mode may be set to one of "sector", "fsdax", and "devdax". Each of those modes is defined by an info-block format that uniquely identifies the mode of operation. The write-infoblock command knows how to generate an "fsdax" or "devdax" info-block relative to the specified image size. The generated block can be written to an existing namespace (provided that namespace is not presently active), written to a file, or piped to standard-out. WARNING: This command is a debug facility that can generate image files with valid infoblocks, but also invalid infoblocks for testing the kernel. Use the --offset and --align options with care. Namely --offset must match the actual physical address offset of the namespace it is applied to, and --align must be one of the architectures supported page sizes. EXAMPLE ------- [verse] ndctl write-infoblock -s 1T -c | ndctl read-infoblock -j wrote 1 infoblock [ { "file":"", "signature":"NVDIMM_PFN_INFO", "uuid":"42e1d574-76ac-402c-9132-5436e31528c0", "parent_uuid":"ef83e49c-4c4a-4fae-b908-72e94675b1b7", "flags":0, "version":"1.4", "dataoff":17196646400, "npfns":264237056, "mode":2, "start_pad":0, "end_trunc":0, "align":16777216, "page_size":4096, "page_struct_size":64 } ] read 1 infoblock OPTIONS ------- :: One or more 'namespaceX.Y' device names. The keyword 'all' can be specified to operate on every namespace in the system, optionally filtered by bus id (see --bus= option), or region id (see --region= option). -c:: --stdout:: Write the infoblock to stdout -o:: --output=:: Write the infoblock to the given file (mutually exclusive with --stdout). -m:: --mode=:: Select the infoblock mode between 'fsdax' and 'devdax'. See linkndctl:ndctl-create-namespace[1] for details on --mode. -s:: --size=:: Override the default size determined from the size of the file specified to --output. In the --stdout case, this option is required. -a:: --align=:: Specify the "align" value in the infoblock. In the --mode=devdax case "align" designates a page mapping size. There is no validation of this value relative to the page mapping capabilities of the platform. -u:: --uuid=:: Override the default autogenerated UUID with the given value. -M:: --map=:: Select whether the page map array is allocated from the device or from "System RAM". Defaults to the device. See linkndctl:ndctl-create-namespace[1] for more details. -p:: --parent-uuid=:: When the infoblock is stored on a labelled namespace the UUID of the namespace must match the "parent uuid" attribute in the infoblock. This option defaults to the UUID of the namespace when --output and --stdout are not used, otherwise it defaults to a NULL UUID (all zeroes). -O:: --offset=:: By default the assumption is that the infoblock is being written to a namespace or namespace-image that is aligned to its size. Specify this EXPERT/DEBUG option to experiment / test the kernel's handling of namespaces that violate that assumption. -r:: --region=:: include::xable-region-options.txt[] include::../copyright.txt[] SEE ALSO -------- linkndctl:ndctl-create-namespace[1], http://www.uefi.org/sites/default/files/resources/UEFI_Spec_2_7.pdf[UEFI NVDIMM Label Protocol] ndctl-81/Documentation/ndctl/ndctl-write-labels.txt000066400000000000000000000012361476737544500226120ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 ndctl-write-labels(1) ===================== NAME ---- ndctl-write-labels - write data to the label area on a dimm SYNOPSIS -------- [verse] 'ndctl write-labels [-i ]' include::labels-description.txt[] Read data from the input filename, or stdin, and write it to the given device. Note that the device must not be active in any region, otherwise the kernel will not allow write access to the device's label data area. OPTIONS ------- include::labels-options.txt[] -i:: --input:: input file SEE ALSO -------- http://www.uefi.org/sites/default/files/resources/UEFI_Spec_2_7.pdf[UEFI NVDIMM Label Protocol] ndctl-81/Documentation/ndctl/ndctl-zero-labels.txt000066400000000000000000000010441476737544500224340ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 ndctl-zero-labels(1) ==================== NAME ---- ndctl-zero-labels - zero out the label area on a dimm or set of dimms SYNOPSIS -------- [verse] 'ndctl zero-labels' [..] [] include::labels-description.txt[] This command resets the device to its default state by deleting all labels. OPTIONS ------- include::labels-options.txt[] include::../copyright.txt[] SEE ALSO -------- http://www.uefi.org/sites/default/files/resources/UEFI_Spec_2_7.pdf[UEFI NVDIMM Label Protocol] ndctl-81/Documentation/ndctl/ndctl.txt000066400000000000000000000026431476737544500202250ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 ndctl(1) ======= NAME ---- ndctl - Manage "libnvdimm" subsystem devices (Non-volatile Memory) SYNOPSIS -------- [verse] 'ndctl' [--version] [--help] [OPTIONS] COMMAND [ARGS] OPTIONS ------- -v:: --version:: Display ndctl version. -h:: --help:: Run ndctl help command. DESCRIPTION ----------- ndctl is utility for managing the "libnvdimm" kernel subsystem. The "libnvdimm" subsystem defines a kernel device model and control message interface for platform NVDIMM resources like those defined by the ACPI 6.0 NFIT (NVDIMM Firmware Interface Table). Operations supported by the tool include, provisioning capacity (namespaces), as well as enumerating/enabling/disabling the devices (dimms, regions, namespaces) associated with an NVDIMM bus. include::../copyright.txt[] SEE ALSO -------- linkndctl:ndctl-create-namespace[1], linkndctl:ndctl-destroy-namespace[1], linkndctl:ndctl-check-namespace[1], linkndctl:ndctl-enable-region[1], linkndctl:ndctl-disable-region[1], linkndctl:ndctl-enable-dimm[1], linkndctl:ndctl-disable-dimm[1], linkndctl:ndctl-enable-namespace[1], linkndctl:ndctl-disable-namespace[1], linkndctl:ndctl-zero-labels[1], linkndctl:ndctl-read-labels[1], linkndctl:ndctl-inject-error[1], linkndctl:ndctl-list[1], https://www.kernel.org/doc/Documentation/nvdimm/nvdimm.txt[LIBNVDIMM Overview], http://pmem.io/documents/NVDIMM_Driver_Writers_Guide.pdf[NVDIMM Driver Writer's Guide] ndctl-81/Documentation/ndctl/region-description.txt000066400000000000000000000005031476737544500227160ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 DESCRIPTION ----------- A generic REGION device is registered for each PMEM range / interleave-set. LIBNVDIMM provides a built-in driver for these REGION devices. This driver is responsible for parsing namespace labels and instantiating PMEM namespaces for each coherent set of labels. ndctl-81/Documentation/ndctl/xable-bus-options.txt000066400000000000000000000004341476737544500224700ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 A bus id number, or a provider string (e.g. "ACPI.NFIT"). Restrict the operation to the specified bus(es). The keyword 'all' can be specified to indicate the lack of any restriction, however this is the same as not supplying a --bus option at all. ndctl-81/Documentation/ndctl/xable-dimm-options.txt000066400000000000000000000004171476737544500226260ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 A 'nmemX' device name, or a dimm id number. Restrict the operation to the specified dimm(s). The keyword 'all' can be specified to indicate the lack of any restriction, however this is the same as not supplying a --dimm option at all. ndctl-81/Documentation/ndctl/xable-namespace-options.txt000066400000000000000000000006161476737544500236350ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 :: A 'namespaceX.Y' device name. The keyword 'all' can be specified to carry out the operation on every namespace in the system, optionally filtered by region (see --region=option) -r:: --region=:: include::xable-region-options.txt[] -b:: --bus=:: include::xable-bus-options.txt[] -v:: --verbose:: Emit debug messages for the namespace operation ndctl-81/Documentation/ndctl/xable-region-options.txt000066400000000000000000000004271476737544500231640ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 A 'regionX' device name, or a region id number. Restrict the operation to the specified region(s). The keyword 'all' can be specified to indicate the lack of any restriction, however this is the same as not supplying a --region option at all. ndctl-81/LICENSES/000077500000000000000000000000001476737544500136435ustar00rootroot00000000000000ndctl-81/LICENSES/other/000077500000000000000000000000001476737544500147645ustar00rootroot00000000000000ndctl-81/LICENSES/other/CC0-1.0000066400000000000000000000143571476737544500155620ustar00rootroot00000000000000Statement of Purpose The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. 1. Copyright and Related Rights. A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; moral rights retained by the original author(s) and/or performer(s); publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; rights protecting the extraction, dissemination, use and reuse of data in a Work; database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. 2. Waiver. To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. 3. Public License Fallback. Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. 4. Limitations and Disclaimers. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. ndctl-81/LICENSES/other/MIT000066400000000000000000000017771476737544500153540ustar00rootroot00000000000000Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ndctl-81/LICENSES/preferred/000077500000000000000000000000001476737544500156215ustar00rootroot00000000000000ndctl-81/LICENSES/preferred/GPL-2.0000066400000000000000000000431031476737544500164640ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. ndctl-81/LICENSES/preferred/LGPL-2.1000066400000000000000000000636251476737544500166140ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! ndctl-81/README.md000066400000000000000000000114551476737544500137230ustar00rootroot00000000000000# ndctl Utility library for managing the libnvdimm (non-volatile memory device) sub-system in the Linux kernel Packaging status Build ===== ``` meson setup build; meson compile -C build; ``` Optionally, to install: ``` meson install -C build ``` There are a number of packages required for the build steps that may not be installed by default. For information about the required packages, see the "BuildRequires:" lines in ndctl.spec.in. https://github.com/pmem/ndctl/blob/main/ndctl.spec.in Documentation ============= See the latest documentation for the NVDIMM kernel sub-system here: https://www.kernel.org/doc/html/latest/driver-api/nvdimm/index.html A getting started guide is also available on the kernel.org nvdimm wiki: https://nvdimm.wiki.kernel.org/start Unit Tests ========== The unit tests run by `meson test` require the nfit_test.ko module to be loaded. To build and install nfit_test.ko: 1. Obtain the kernel source. For example, `git clone -b libnvdimm-for-next git://git.kernel.org/pub/scm/linux/kernel/git/nvdimm/nvdimm.git` 1. Skip to step 3 if the kernel version is >= v4.8. Otherwise, for kernel versions < v4.8, configure the kernel to make some memory available to CMA (contiguous memory allocator). This will be used to emulate DAX. ``` CONFIG_DMA_CMA=y CONFIG_CMA_SIZE_MBYTES=200 ``` **or** `cma=200M` on the kernel command line. 1. Compile the libnvdimm sub-system as a module, make sure "zone device" memory is enabled, and enable the btt, pfn, and dax features of the sub-system: ``` CONFIG_X86_PMEM_LEGACY=m CONFIG_ZONE_DEVICE=y CONFIG_LIBNVDIMM=m CONFIG_BLK_DEV_PMEM=m CONFIG_BTT=y CONFIG_NVDIMM_PFN=y CONFIG_NVDIMM_DAX=y CONFIG_DEV_DAX_PMEM=m CONFIG_ENCRYPTED_KEYS=y CONFIG_NVDIMM_SECURITY_TEST=y CONFIG_STRICT_DEVMEM=y CONFIG_IO_STRICT_DEVMEM=y ``` 1. Build and install the unit test enabled libnvdimm modules in the following order. The unit test modules need to be in place prior to the `depmod` that runs during the final `modules_install` ``` make M=tools/testing/nvdimm sudo make M=tools/testing/nvdimm modules_install sudo make modules_install ``` 1. CXL test The unit tests will also run CXL tests by default. In order to make these work, we need to install the cxl_test.ko as well. Obtain the CXL kernel source(optional). For example, `git clone -b pending git://git.kernel.org/pub/scm/linux/kernel/git/cxl/cxl.git` Enable CXL-related kernel configuration options. ``` CONFIG_CXL_BUS=m CONFIG_CXL_PCI=m CONFIG_CXL_ACPI=m CONFIG_CXL_PMEM=m CONFIG_CXL_MEM=m CONFIG_CXL_PORT=m CONFIG_CXL_REGION=y CONFIG_CXL_REGION_INVALIDATION_TEST=y CONFIG_DEV_DAX_CXL=m ``` 1. Install cxl_test and related mock modules. ``` make M=tools/testing/cxl sudo make M=tools/testing/cxl modules_install sudo make modules_install ``` 1. Now run `meson test -C build` in the ndctl source directory, or `ndctl test`, if ndctl was built with `-Dtest=enabled` as a configuration option to meson. 1. To run the 'destructive' set of tests that may clobber existing pmem configurations and data, configure meson with the destructive option after the `meson setup` step: ``` meson configure -Dtest=enabled -Ddestructive=enabled build; ``` Troubleshooting =============== The unit tests will validate that the environment is set up correctly before they try to run. If the platform is misconfigured, i.e. the unit test modules are not available, or the test versions of the modules are superseded by the "in-tree/production" version of the modules `meson test` will skip tests and report a message like the following in `build/meson-logs/testlog.txt` ``` SKIP: libndctl ============== test/init: nfit_test_init: nfit.ko: appears to be production version: /lib/modules/4.8.8-200.fc24.x86_64/kernel/drivers/acpi/nfit/nfit.ko.xz __ndctl_test_skip: explicit skip test_libndctl:2684 nfit_test unavailable skipping tests ``` If the unit test modules are indeed available in the modules 'extra' directory the default depmod policy can be overridden by adding a file to /etc/depmod.d with the following contents: ``` override nfit * extra override device_dax * extra override dax_pmem * extra override dax_pmem_core * extra override dax_pmem_compat * extra override libnvdimm * extra override nd_btt * extra override nd_e820 * extra override nd_pmem * extra ``` The nfit_test module emulates pmem with memory allocated via vmalloc(). One of the side effects is that this breaks 'physically contiguous' assumptions in the driver. Use the '--align=4K option to 'ndctl create-namespace' to avoid these corner case scenarios. ndctl-81/acpi.h000066400000000000000000000106151476737544500135260ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (C) 2013-2020 Intel Corporation. All rights reserved. */ #ifndef __ACPI_H__ #define __ACPI_H__ #include #include static const uuid_le uuid_pmem = UUID_LE(0x66f0d379, 0xb4f3, 0x4074, 0xac, 0x43, 0x0d, 0x33, 0x18, 0xb7, 0x8c, 0xdb); static inline void nfit_spa_uuid_pm(void *uuid) { memcpy(uuid, &uuid_pmem, 16); } enum { NFIT_TABLE_SPA = 0, SRAT_TABLE_MEM = 1, SRAT_MEM_ENABLED = (1<<0), SRAT_MEM_HOT_PLUGGABLE = (1<<1), SRAT_MEM_NON_VOLATILE = (1<<2), }; /** * struct nfit - Nvdimm Firmware Interface Table * @signature: "ACPI" * @length: sum of size of this table plus all appended subtables */ struct acpi_header { uint8_t signature[4]; uint32_t length; uint8_t revision; uint8_t checksum; uint8_t oemid[6]; uint64_t oem_tbl_id; uint32_t oem_revision; uint32_t asl_id; uint32_t asl_revision; } __attribute__((packed)); struct nfit { struct acpi_header h; uint32_t reserved; } __attribute__((packed)); enum acpi_nfit_type { ACPI_NFIT_TYPE_SYSTEM_ADDRESS = 0, ACPI_NFIT_TYPE_MEMORY_MAP = 1, ACPI_NFIT_TYPE_INTERLEAVE = 2, ACPI_NFIT_TYPE_SMBIOS = 3, ACPI_NFIT_TYPE_CONTROL_REGION = 4, ACPI_NFIT_TYPE_DATA_REGION = 5, ACPI_NFIT_TYPE_FLUSH_ADDRESS = 6, ACPI_NFIT_TYPE_CAPABILITIES = 7, ACPI_NFIT_TYPE_RESERVED = 8 /* 8 and greater are reserved */ }; /** * struct nfit_spa - System Physical Address Range Descriptor Table */ struct nfit_spa { uint16_t type; uint16_t length; uint16_t range_index; uint16_t flags; uint32_t reserved; uint32_t proximity_domain; uint8_t type_uuid[16]; uint64_t spa_base; uint64_t spa_length; uint64_t mem_attr; } __attribute__((packed)); struct nfit_map { uint16_t type; uint16_t length; uint32_t device_handle; uint16_t physical_id; uint16_t region_id; uint16_t range_index; uint16_t region_index; uint64_t region_size; uint64_t region_offset; uint64_t address; uint16_t interleave_index; uint16_t interleave_ways; uint16_t flags; uint16_t reserved; /* Reserved, must be zero */ } __attribute__((packed)); struct srat { struct acpi_header h; uint32_t revision; uint64_t reserved; } __attribute__((packed)); enum acpi_srat_type { ACPI_SRAT_TYPE_CPU_AFFINITY = 0, ACPI_SRAT_TYPE_MEMORY_AFFINITY = 1, ACPI_SRAT_TYPE_X2APIC_CPU_AFFINITY = 2, ACPI_SRAT_TYPE_GICC_AFFINITY = 3, ACPI_SRAT_TYPE_GIC_ITS_AFFINITY = 4, /* ACPI 6.2 */ ACPI_SRAT_TYPE_GENERIC_AFFINITY = 5, /* ACPI 6.3 */ ACPI_SRAT_TYPE_RESERVED = 6 /* 5 and greater are reserved */ }; struct srat_cpu { uint8_t type; uint8_t length; uint8_t proximity_domain_lo; uint8_t apic_id; uint32_t flags; uint8_t local_sapic_eid; uint8_t proximity_domain_hi[3]; uint32_t clock_domain; } __attribute__((packed)); struct srat_generic { uint8_t type; uint8_t length; uint8_t reserved; uint8_t device_handle_type; uint32_t proximity_domain; uint8_t device_handle[16]; uint32_t flags; uint32_t reserved1; } __attribute__((packed)); struct srat_mem { uint8_t type; uint8_t length; uint32_t proximity_domain; uint16_t reserved; uint64_t spa_base; uint64_t spa_length; uint32_t reserved1; uint32_t flags; uint64_t reserved2; } __attribute__((packed)); struct acpi_subtable8 { uint8_t type; uint8_t length; uint8_t buf[]; } __attribute__((packed)); struct acpi_subtable16 { uint16_t type; uint16_t length; uint8_t buf[]; } __attribute__((packed)); struct slit { struct acpi_header h; uint64_t count; uint8_t entry[]; /* size = count^2 */ } __attribute__((packed)); static inline unsigned char acpi_checksum(void *buf, size_t size) { unsigned char sum, *data = buf; size_t i; for (sum = 0, i = 0; i < size; i++) sum += data[i]; return 0 - sum; } static inline void writeq(uint64_t v, void *a) { uint64_t *p = a; *p = htole64(v); } static inline void writel(uint32_t v, void *a) { uint32_t *p = a; *p = htole32(v); } static inline void writew(unsigned short v, void *a) { unsigned short *p = a; *p = htole16(v); } static inline void writeb(unsigned char v, void *a) { unsigned char *p = a; *p = v; } static inline uint64_t readq(void *a) { uint64_t *p = a; return le64toh(*p); } static inline uint32_t readl(void *a) { uint32_t *p = a; return le32toh(*p); } static inline uint16_t readw(void *a) { uint16_t *p = a; return le16toh(*p); } static inline uint8_t readb(void *a) { uint8_t *p = a; return *p; } #endif /* __ACPI_H__ */ ndctl-81/ccan/000077500000000000000000000000001476737544500133425ustar00rootroot00000000000000ndctl-81/ccan/array_size/000077500000000000000000000000001476737544500155125ustar00rootroot00000000000000ndctl-81/ccan/array_size/array_size.h000066400000000000000000000015471476737544500200420ustar00rootroot00000000000000/* SPDX-License-Identifier: CC0-1.0 */ #ifndef CCAN_ARRAY_SIZE_H #define CCAN_ARRAY_SIZE_H #include "config.h" #include /** * ARRAY_SIZE - get the number of elements in a visible array * @arr: the array whose size you want. * * This does not work on pointers, or arrays declared as [], or * function parameters. With correct compiler support, such usage * will cause a build error (see build_assert). */ #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]) + _array_size_chk(arr)) #if HAVE_BUILTIN_TYPES_COMPATIBLE_P && HAVE_TYPEOF /* Two gcc extensions. * &a[0] degrades to a pointer: a different type from an array */ #define _array_size_chk(arr) \ BUILD_ASSERT_OR_ZERO(!__builtin_types_compatible_p(typeof(arr), \ typeof(&(arr)[0]))) #else #define _array_size_chk(arr) 0 #endif #endif /* CCAN_ALIGNOF_H */ ndctl-81/ccan/build_assert/000077500000000000000000000000001476737544500160225ustar00rootroot00000000000000ndctl-81/ccan/build_assert/build_assert.h000066400000000000000000000022721476737544500206560ustar00rootroot00000000000000/* SPDX-License-Identifier: CC0-1.0 */ #ifndef CCAN_BUILD_ASSERT_H #define CCAN_BUILD_ASSERT_H /** * BUILD_ASSERT - assert a build-time dependency. * @cond: the compile-time condition which must be true. * * Your compile will fail if the condition isn't true, or can't be evaluated * by the compiler. This can only be used within a function. * * Example: * #include * ... * static char *foo_to_char(struct foo *foo) * { * // This code needs string to be at start of foo. * BUILD_ASSERT(offsetof(struct foo, string) == 0); * return (char *)foo; * } */ #define BUILD_ASSERT(cond) \ do { (void) sizeof(char [1 - 2*!(cond)]); } while(0) /** * BUILD_ASSERT_OR_ZERO - assert a build-time dependency, as an expression. * @cond: the compile-time condition which must be true. * * Your compile will fail if the condition isn't true, or can't be evaluated * by the compiler. This can be used in an expression: its value is "0". * * Example: * #define foo_to_char(foo) \ * ((char *)(foo) \ * + BUILD_ASSERT_OR_ZERO(offsetof(struct foo, string) == 0)) */ #define BUILD_ASSERT_OR_ZERO(cond) \ (sizeof(char [1 - 2*!(cond)]) - 1) #endif /* CCAN_BUILD_ASSERT_H */ ndctl-81/ccan/check_type/000077500000000000000000000000001476737544500154605ustar00rootroot00000000000000ndctl-81/ccan/check_type/check_type.h000066400000000000000000000044631476737544500177560ustar00rootroot00000000000000/* SPDX-License-Identifier: CC0-1.0 */ #ifndef CCAN_CHECK_TYPE_H #define CCAN_CHECK_TYPE_H #include "config.h" /** * check_type - issue a warning or build failure if type is not correct. * @expr: the expression whose type we should check (not evaluated). * @type: the exact type we expect the expression to be. * * This macro is usually used within other macros to try to ensure that a macro * argument is of the expected type. No type promotion of the expression is * done: an unsigned int is not the same as an int! * * check_type() always evaluates to 0. * * If your compiler does not support typeof, then the best we can do is fail * to compile if the sizes of the types are unequal (a less complete check). * * Example: * // They should always pass a 64-bit value to _set_some_value! * #define set_some_value(expr) \ * _set_some_value((check_type((expr), uint64_t), (expr))) */ /** * check_types_match - issue a warning or build failure if types are not same. * @expr1: the first expression (not evaluated). * @expr2: the second expression (not evaluated). * * This macro is usually used within other macros to try to ensure that * arguments are of identical types. No type promotion of the expressions is * done: an unsigned int is not the same as an int! * * check_types_match() always evaluates to 0. * * If your compiler does not support typeof, then the best we can do is fail * to compile if the sizes of the types are unequal (a less complete check). * * Example: * // Do subtraction to get to enclosing type, but make sure that * // pointer is of correct type for that member. * #define container_of(mbr_ptr, encl_type, mbr) \ * (check_types_match((mbr_ptr), &((encl_type *)0)->mbr), \ * ((encl_type *) \ * ((char *)(mbr_ptr) - offsetof(enclosing_type, mbr)))) */ #if HAVE_TYPEOF #define check_type(expr, type) \ ((typeof(expr) *)0 != (type *)0) #define check_types_match(expr1, expr2) \ ((typeof(expr1) *)0 != (typeof(expr2) *)0) #else #include /* Without typeof, we can only test the sizes. */ #define check_type(expr, type) \ BUILD_ASSERT_OR_ZERO(sizeof(expr) == sizeof(type)) #define check_types_match(expr1, expr2) \ BUILD_ASSERT_OR_ZERO(sizeof(expr1) == sizeof(expr2)) #endif /* HAVE_TYPEOF */ #endif /* CCAN_CHECK_TYPE_H */ ndctl-81/ccan/container_of/000077500000000000000000000000001476737544500160105ustar00rootroot00000000000000ndctl-81/ccan/container_of/container_of.h000066400000000000000000000061121476737544500206270ustar00rootroot00000000000000/* SPDX-License-Identifier: CC0-1.0 */ #ifndef CCAN_CONTAINER_OF_H #define CCAN_CONTAINER_OF_H #include #include "config.h" #include /** * container_of - get pointer to enclosing structure * @member_ptr: pointer to the structure member * @containing_type: the type this member is within * @member: the name of this member within the structure. * * Given a pointer to a member of a structure, this macro does pointer * subtraction to return the pointer to the enclosing type. * * Example: * struct foo { * int fielda, fieldb; * // ... * }; * struct info { * int some_other_field; * struct foo my_foo; * }; * * static struct info *foo_to_info(struct foo *foo) * { * return container_of(foo, struct info, my_foo); * } */ #define container_of(member_ptr, containing_type, member) \ ((containing_type *) \ ((char *)(member_ptr) \ - container_off(containing_type, member)) \ + check_types_match(*(member_ptr), ((containing_type *)0)->member)) /** * container_off - get offset to enclosing structure * @containing_type: the type this member is within * @member: the name of this member within the structure. * * Given a pointer to a member of a structure, this macro does * typechecking and figures out the offset to the enclosing type. * * Example: * struct foo { * int fielda, fieldb; * // ... * }; * struct info { * int some_other_field; * struct foo my_foo; * }; * * static struct info *foo_to_info(struct foo *foo) * { * size_t off = container_off(struct info, my_foo); * return (void *)((char *)foo - off); * } */ #define container_off(containing_type, member) \ offsetof(containing_type, member) /** * container_of_var - get pointer to enclosing structure using a variable * @member_ptr: pointer to the structure member * @container_var: a pointer of same type as this member's container * @member: the name of this member within the structure. * * Given a pointer to a member of a structure, this macro does pointer * subtraction to return the pointer to the enclosing type. * * Example: * static struct info *foo_to_i(struct foo *foo) * { * struct info *i = container_of_var(foo, i, my_foo); * return i; * } */ #if HAVE_TYPEOF #define container_of_var(member_ptr, container_var, member) \ container_of(member_ptr, typeof(*container_var), member) #else #define container_of_var(member_ptr, container_var, member) \ ((void *)((char *)(member_ptr) - \ container_off_var(container_var, member))) #endif /** * container_off_var - get offset of a field in enclosing structure * @container_var: a pointer to a container structure * @member: the name of a member within the structure. * * Given (any) pointer to a structure and a its member name, this * macro does pointer subtraction to return offset of member in a * structure memory layout. * */ #if HAVE_TYPEOF #define container_off_var(var, member) \ container_off(typeof(*var), member) #else #define container_off_var(var, member) \ ((const char *)&(var)->member - (const char *)(var)) #endif #endif /* CCAN_CONTAINER_OF_H */ ndctl-81/ccan/endian/000077500000000000000000000000001476737544500146005ustar00rootroot00000000000000ndctl-81/ccan/endian/endian.h000066400000000000000000000217611476737544500162160ustar00rootroot00000000000000/* SPDX-License-Identifier: CC0-1.0 */ #ifndef CCAN_ENDIAN_H #define CCAN_ENDIAN_H #include #include "config.h" /** * BSWAP_16 - reverse bytes in a constant uint16_t value. * @val: constant value whose bytes to swap. * * Designed to be usable in constant-requiring initializers. * * Example: * struct mystruct { * char buf[BSWAP_16(0x1234)]; * }; */ #define BSWAP_16(val) \ ((((uint16_t)(val) & 0x00ff) << 8) \ | (((uint16_t)(val) & 0xff00) >> 8)) /** * BSWAP_32 - reverse bytes in a constant uint32_t value. * @val: constant value whose bytes to swap. * * Designed to be usable in constant-requiring initializers. * * Example: * struct mystruct { * char buf[BSWAP_32(0xff000000)]; * }; */ #define BSWAP_32(val) \ ((((uint32_t)(val) & 0x000000ff) << 24) \ | (((uint32_t)(val) & 0x0000ff00) << 8) \ | (((uint32_t)(val) & 0x00ff0000) >> 8) \ | (((uint32_t)(val) & 0xff000000) >> 24)) /** * BSWAP_64 - reverse bytes in a constant uint64_t value. * @val: constantvalue whose bytes to swap. * * Designed to be usable in constant-requiring initializers. * * Example: * struct mystruct { * char buf[BSWAP_64(0xff00000000000000ULL)]; * }; */ #define BSWAP_64(val) \ ((((uint64_t)(val) & 0x00000000000000ffULL) << 56) \ | (((uint64_t)(val) & 0x000000000000ff00ULL) << 40) \ | (((uint64_t)(val) & 0x0000000000ff0000ULL) << 24) \ | (((uint64_t)(val) & 0x00000000ff000000ULL) << 8) \ | (((uint64_t)(val) & 0x000000ff00000000ULL) >> 8) \ | (((uint64_t)(val) & 0x0000ff0000000000ULL) >> 24) \ | (((uint64_t)(val) & 0x00ff000000000000ULL) >> 40) \ | (((uint64_t)(val) & 0xff00000000000000ULL) >> 56)) #if HAVE_BYTESWAP_H #include #else /** * bswap_16 - reverse bytes in a uint16_t value. * @val: value whose bytes to swap. * * Example: * // Output contains "1024 is 4 as two bytes reversed" * printf("1024 is %u as two bytes reversed\n", bswap_16(1024)); */ static inline uint16_t bswap_16(uint16_t val) { return BSWAP_16(val); } /** * bswap_32 - reverse bytes in a uint32_t value. * @val: value whose bytes to swap. * * Example: * // Output contains "1024 is 262144 as four bytes reversed" * printf("1024 is %u as four bytes reversed\n", bswap_32(1024)); */ static inline uint32_t bswap_32(uint32_t val) { return BSWAP_32(val); } #endif /* !HAVE_BYTESWAP_H */ #if !HAVE_BSWAP_64 /** * bswap_64 - reverse bytes in a uint64_t value. * @val: value whose bytes to swap. * * Example: * // Output contains "1024 is 1125899906842624 as eight bytes reversed" * printf("1024 is %llu as eight bytes reversed\n", * (unsigned long long)bswap_64(1024)); */ static inline uint64_t bswap_64(uint64_t val) { return BSWAP_64(val); } #endif /* Sanity check the defines. We don't handle weird endianness. */ #if !HAVE_LITTLE_ENDIAN && !HAVE_BIG_ENDIAN #error "Unknown endian" #elif HAVE_LITTLE_ENDIAN && HAVE_BIG_ENDIAN #error "Can't compile for both big and little endian." #endif #ifdef __CHECKER__ /* sparse needs forcing to remove bitwise attribute from ccan/short_types */ #define ENDIAN_CAST __attribute__((force)) #define ENDIAN_TYPE __attribute__((bitwise)) #else #define ENDIAN_CAST #define ENDIAN_TYPE #endif typedef uint64_t ENDIAN_TYPE leint64_t; typedef uint64_t ENDIAN_TYPE beint64_t; typedef uint32_t ENDIAN_TYPE leint32_t; typedef uint32_t ENDIAN_TYPE beint32_t; typedef uint16_t ENDIAN_TYPE leint16_t; typedef uint16_t ENDIAN_TYPE beint16_t; #if HAVE_LITTLE_ENDIAN /** * CPU_TO_LE64 - convert a constant uint64_t value to little-endian * @native: constant to convert */ #define CPU_TO_LE64(native) ((ENDIAN_CAST leint64_t)(native)) /** * CPU_TO_LE32 - convert a constant uint32_t value to little-endian * @native: constant to convert */ #define CPU_TO_LE32(native) ((ENDIAN_CAST leint32_t)(native)) /** * CPU_TO_LE16 - convert a constant uint16_t value to little-endian * @native: constant to convert */ #define CPU_TO_LE16(native) ((ENDIAN_CAST leint16_t)(native)) /** * LE64_TO_CPU - convert a little-endian uint64_t constant * @le_val: little-endian constant to convert */ #define LE64_TO_CPU(le_val) ((ENDIAN_CAST uint64_t)(le_val)) /** * LE32_TO_CPU - convert a little-endian uint32_t constant * @le_val: little-endian constant to convert */ #define LE32_TO_CPU(le_val) ((ENDIAN_CAST uint32_t)(le_val)) /** * LE16_TO_CPU - convert a little-endian uint16_t constant * @le_val: little-endian constant to convert */ #define LE16_TO_CPU(le_val) ((ENDIAN_CAST uint16_t)(le_val)) #else /* ... HAVE_BIG_ENDIAN */ #define CPU_TO_LE64(native) ((ENDIAN_CAST leint64_t)BSWAP_64(native)) #define CPU_TO_LE32(native) ((ENDIAN_CAST leint32_t)BSWAP_32(native)) #define CPU_TO_LE16(native) ((ENDIAN_CAST leint16_t)BSWAP_16(native)) #define LE64_TO_CPU(le_val) BSWAP_64((ENDIAN_CAST uint64_t)le_val) #define LE32_TO_CPU(le_val) BSWAP_32((ENDIAN_CAST uint32_t)le_val) #define LE16_TO_CPU(le_val) BSWAP_16((ENDIAN_CAST uint16_t)le_val) #endif /* HAVE_BIG_ENDIAN */ #if HAVE_BIG_ENDIAN /** * CPU_TO_BE64 - convert a constant uint64_t value to big-endian * @native: constant to convert */ #define CPU_TO_BE64(native) ((ENDIAN_CAST beint64_t)(native)) /** * CPU_TO_BE32 - convert a constant uint32_t value to big-endian * @native: constant to convert */ #define CPU_TO_BE32(native) ((ENDIAN_CAST beint32_t)(native)) /** * CPU_TO_BE16 - convert a constant uint16_t value to big-endian * @native: constant to convert */ #define CPU_TO_BE16(native) ((ENDIAN_CAST beint16_t)(native)) /** * BE64_TO_CPU - convert a big-endian uint64_t constant * @le_val: big-endian constant to convert */ #define BE64_TO_CPU(le_val) ((ENDIAN_CAST uint64_t)(le_val)) /** * BE32_TO_CPU - convert a big-endian uint32_t constant * @le_val: big-endian constant to convert */ #define BE32_TO_CPU(le_val) ((ENDIAN_CAST uint32_t)(le_val)) /** * BE16_TO_CPU - convert a big-endian uint16_t constant * @le_val: big-endian constant to convert */ #define BE16_TO_CPU(le_val) ((ENDIAN_CAST uint16_t)(le_val)) #else /* ... HAVE_LITTLE_ENDIAN */ #define CPU_TO_BE64(native) ((ENDIAN_CAST beint64_t)BSWAP_64(native)) #define CPU_TO_BE32(native) ((ENDIAN_CAST beint32_t)BSWAP_32(native)) #define CPU_TO_BE16(native) ((ENDIAN_CAST beint16_t)BSWAP_16(native)) #define BE64_TO_CPU(le_val) BSWAP_64((ENDIAN_CAST uint64_t)le_val) #define BE32_TO_CPU(le_val) BSWAP_32((ENDIAN_CAST uint32_t)le_val) #define BE16_TO_CPU(le_val) BSWAP_16((ENDIAN_CAST uint16_t)le_val) #endif /* HAVE_LITTE_ENDIAN */ /** * cpu_to_le64 - convert a uint64_t value to little-endian * @native: value to convert */ static inline leint64_t cpu_to_le64(uint64_t native) { return CPU_TO_LE64(native); } /** * cpu_to_le32 - convert a uint32_t value to little-endian * @native: value to convert */ static inline leint32_t cpu_to_le32(uint32_t native) { return CPU_TO_LE32(native); } /** * cpu_to_le16 - convert a uint16_t value to little-endian * @native: value to convert */ static inline leint16_t cpu_to_le16(uint16_t native) { return CPU_TO_LE16(native); } /** * le64_to_cpu - convert a little-endian uint64_t value * @le_val: little-endian value to convert */ static inline uint64_t le64_to_cpu(leint64_t le_val) { return LE64_TO_CPU(le_val); } /** * le32_to_cpu - convert a little-endian uint32_t value * @le_val: little-endian value to convert */ static inline uint32_t le32_to_cpu(leint32_t le_val) { return LE32_TO_CPU(le_val); } /** * le16_to_cpu - convert a little-endian uint16_t value * @le_val: little-endian value to convert */ static inline uint16_t le16_to_cpu(leint16_t le_val) { return LE16_TO_CPU(le_val); } /** * cpu_to_be64 - convert a uint64_t value to big endian. * @native: value to convert */ static inline beint64_t cpu_to_be64(uint64_t native) { return ((ENDIAN_CAST beint64_t)BSWAP_64(native)); return CPU_TO_BE64(native); } /** * cpu_to_be32 - convert a uint32_t value to big endian. * @native: value to convert */ static inline beint32_t cpu_to_be32(uint32_t native) { return CPU_TO_BE32(native); } /** * cpu_to_be16 - convert a uint16_t value to big endian. * @native: value to convert */ static inline beint16_t cpu_to_be16(uint16_t native) { return CPU_TO_BE16(native); } /** * be64_to_cpu - convert a big-endian uint64_t value * @be_val: big-endian value to convert */ static inline uint64_t be64_to_cpu(beint64_t be_val) { return BE64_TO_CPU(be_val); } /** * be32_to_cpu - convert a big-endian uint32_t value * @be_val: big-endian value to convert */ static inline uint32_t be32_to_cpu(beint32_t be_val) { return BE32_TO_CPU(be_val); } /** * be16_to_cpu - convert a big-endian uint16_t value * @be_val: big-endian value to convert */ static inline uint16_t be16_to_cpu(beint16_t be_val) { return BE16_TO_CPU(be_val); } /* Whichever they include first, they get these definitions. */ #ifdef CCAN_SHORT_TYPES_H /** * be64/be32/be16 - 64/32/16 bit big-endian representation. */ typedef beint64_t be64; typedef beint32_t be32; typedef beint16_t be16; /** * le64/le32/le16 - 64/32/16 bit little-endian representation. */ typedef leint64_t le64; typedef leint32_t le32; typedef leint16_t le16; #endif #endif /* CCAN_ENDIAN_H */ ndctl-81/ccan/list/000077500000000000000000000000001476737544500143155ustar00rootroot00000000000000ndctl-81/ccan/list/list.c000066400000000000000000000017141476737544500154370ustar00rootroot00000000000000// SPDX-License-Identifier: MIT #include #include #include "list.h" static void *corrupt(const char *abortstr, const struct list_node *head, const struct list_node *node, unsigned int count) { if (abortstr) { fprintf(stderr, "%s: prev corrupt in node %p (%u) of %p\n", abortstr, node, count, head); abort(); } return NULL; } struct list_node *list_check_node(const struct list_node *node, const char *abortstr) { const struct list_node *p, *n; int count = 0; for (p = node, n = node->next; n != node; p = n, n = n->next) { count++; if (n->prev != p) return corrupt(abortstr, node, n, count); } /* Check prev on head node. */ if (node->prev != p) return corrupt(abortstr, node, node, 0); return (struct list_node *)node; } struct list_head *list_check(const struct list_head *h, const char *abortstr) { if (!list_check_node(&h->n, abortstr)) return NULL; return (struct list_head *)h; } ndctl-81/ccan/list/list.h000066400000000000000000000573321476737544500154530ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ #ifndef CCAN_LIST_H #define CCAN_LIST_H //#define CCAN_LIST_DEBUG 1 #include #include #include #include #include /** * struct list_node - an entry in a doubly-linked list * @next: next entry (self if empty) * @prev: previous entry (self if empty) * * This is used as an entry in a linked list. * Example: * struct child { * const char *name; * // Linked list of all us children. * struct list_node list; * }; */ struct list_node { struct list_node *next, *prev; }; /** * struct list_head - the head of a doubly-linked list * @h: the list_head (containing next and prev pointers) * * This is used as the head of a linked list. * Example: * struct parent { * const char *name; * struct list_head children; * unsigned int num_children; * }; */ struct list_head { struct list_node n; }; /** * list_check - check head of a list for consistency * @h: the list_head * @abortstr: the location to print on aborting, or NULL. * * Because list_nodes have redundant information, consistency checking between * the back and forward links can be done. This is useful as a debugging check. * If @abortstr is non-NULL, that will be printed in a diagnostic if the list * is inconsistent, and the function will abort. * * Returns the list head if the list is consistent, NULL if not (it * can never return NULL if @abortstr is set). * * See also: list_check_node() * * Example: * static void dump_parent(struct parent *p) * { * struct child *c; * * printf("%s (%u children):\n", p->name, p->num_children); * list_check(&p->children, "bad child list"); * list_for_each(&p->children, c, list) * printf(" -> %s\n", c->name); * } */ struct list_head *list_check(const struct list_head *h, const char *abortstr); /** * list_check_node - check node of a list for consistency * @n: the list_node * @abortstr: the location to print on aborting, or NULL. * * Check consistency of the list node is in (it must be in one). * * See also: list_check() * * Example: * static void dump_child(const struct child *c) * { * list_check_node(&c->list, "bad child list"); * printf("%s\n", c->name); * } */ struct list_node *list_check_node(const struct list_node *n, const char *abortstr); #define LIST_LOC __FILE__ ":" stringify(__LINE__) #ifdef CCAN_LIST_DEBUG #define list_debug(h, loc) list_check((h), loc) #define list_debug_node(n, loc) list_check_node((n), loc) #else #define list_debug(h, loc) ((void)loc, h) #define list_debug_node(n, loc) ((void)loc, n) #endif /** * LIST_HEAD_INIT - initializer for an empty list_head * @name: the name of the list. * * Explicit initializer for an empty list. * * See also: * LIST_HEAD, list_head_init() * * Example: * static struct list_head my_list = LIST_HEAD_INIT(my_list); */ #define LIST_HEAD_INIT(name) { { &(name).n, &(name).n } } /** * LIST_HEAD - define and initialize an empty list_head * @name: the name of the list. * * The LIST_HEAD macro defines a list_head and initializes it to an empty * list. It can be prepended by "static" to define a static list_head. * * See also: * LIST_HEAD_INIT, list_head_init() * * Example: * static LIST_HEAD(my_global_list); */ #define LIST_HEAD(name) \ struct list_head name = LIST_HEAD_INIT(name) /** * list_head_init - initialize a list_head * @h: the list_head to set to the empty list * * Example: * ... * struct parent *parent = malloc(sizeof(*parent)); * * list_head_init(&parent->children); * parent->num_children = 0; */ static inline void list_head_init(struct list_head *h) { h->n.next = h->n.prev = &h->n; } /** * list_node_init - initialize a list_node * @n: the list_node to link to itself. * * You don't need to use this normally! But it lets you list_del(@n) * safely. */ static inline void list_node_init(struct list_node *n) { n->next = n->prev = n; } /** * list_add_after - add an entry after an existing node in a linked list * @h: the list_head to add the node to (for debugging) * @p: the existing list_node to add the node after * @n: the new list_node to add to the list. * * The existing list_node must already be a member of the list. * The new list_node does not need to be initialized; it will be overwritten. * * Example: * struct child c1, c2, c3; * LIST_HEAD(h); * * list_add_tail(&h, &c1.list); * list_add_tail(&h, &c3.list); * list_add_after(&h, &c1.list, &c2.list); */ #define list_add_after(h, p, n) list_add_after_(h, p, n, LIST_LOC) static inline void list_add_after_(struct list_head *h, struct list_node *p, struct list_node *n, const char *abortstr) { n->next = p->next; n->prev = p; p->next->prev = n; p->next = n; (void)list_debug(h, abortstr); } /** * list_add - add an entry at the start of a linked list. * @h: the list_head to add the node to * @n: the list_node to add to the list. * * The list_node does not need to be initialized; it will be overwritten. * Example: * struct child *child = malloc(sizeof(*child)); * * child->name = "marvin"; * list_add(&parent->children, &child->list); * parent->num_children++; */ #define list_add(h, n) list_add_(h, n, LIST_LOC) static inline void list_add_(struct list_head *h, struct list_node *n, const char *abortstr) { list_add_after_(h, &h->n, n, abortstr); } /** * list_add_before - add an entry before an existing node in a linked list * @h: the list_head to add the node to (for debugging) * @p: the existing list_node to add the node before * @n: the new list_node to add to the list. * * The existing list_node must already be a member of the list. * The new list_node does not need to be initialized; it will be overwritten. * * Example: * list_head_init(&h); * list_add_tail(&h, &c1.list); * list_add_tail(&h, &c3.list); * list_add_before(&h, &c3.list, &c2.list); */ #define list_add_before(h, p, n) list_add_before_(h, p, n, LIST_LOC) static inline void list_add_before_(struct list_head *h, struct list_node *p, struct list_node *n, const char *abortstr) { n->next = p; n->prev = p->prev; p->prev->next = n; p->prev = n; (void)list_debug(h, abortstr); } /** * list_add_tail - add an entry at the end of a linked list. * @h: the list_head to add the node to * @n: the list_node to add to the list. * * The list_node does not need to be initialized; it will be overwritten. * Example: * list_add_tail(&parent->children, &child->list); * parent->num_children++; */ #define list_add_tail(h, n) list_add_tail_(h, n, LIST_LOC) static inline void list_add_tail_(struct list_head *h, struct list_node *n, const char *abortstr) { list_add_before_(h, &h->n, n, abortstr); } /** * list_empty - is a list empty? * @h: the list_head * * If the list is empty, returns true. * * Example: * assert(list_empty(&parent->children) == (parent->num_children == 0)); */ #define list_empty(h) list_empty_(h, LIST_LOC) static inline bool list_empty_(const struct list_head *h, const char* abortstr) { (void)list_debug(h, abortstr); return h->n.next == &h->n; } /** * list_empty_nodebug - is a list empty (and don't perform debug checks)? * @h: the list_head * * If the list is empty, returns true. * This differs from list_empty() in that if CCAN_LIST_DEBUG is set it * will NOT perform debug checks. Only use this function if you REALLY * know what you're doing. * * Example: * assert(list_empty_nodebug(&parent->children) == (parent->num_children == 0)); */ #ifndef CCAN_LIST_DEBUG #define list_empty_nodebug(h) list_empty(h) #else static inline bool list_empty_nodebug(const struct list_head *h) { return h->n.next == &h->n; } #endif /** * list_empty_nocheck - is a list empty? * @h: the list_head * * If the list is empty, returns true. This doesn't perform any * debug check for list consistency, so it can be called without * locks, racing with the list being modified. This is ok for * checks where an incorrect result is not an issue (optimized * bail out path for example). */ static inline bool list_empty_nocheck(const struct list_head *h) { return h->n.next == &h->n; } /** * list_del - delete an entry from an (unknown) linked list. * @n: the list_node to delete from the list. * * Note that this leaves @n in an undefined state; it can be added to * another list, but not deleted again. * * See also: * list_del_from(), list_del_init() * * Example: * list_del(&child->list); * parent->num_children--; */ #define list_del(n) list_del_(n, LIST_LOC) static inline void list_del_(struct list_node *n, const char* abortstr) { (void)list_debug_node(n, abortstr); n->next->prev = n->prev; n->prev->next = n->next; #ifdef CCAN_LIST_DEBUG /* Catch use-after-del. */ n->next = n->prev = NULL; #endif } /** * list_del_init - delete a node, and reset it so it can be deleted again. * @n: the list_node to be deleted. * * list_del(@n) or list_del_init() again after this will be safe, * which can be useful in some cases. * * See also: * list_del_from(), list_del() * * Example: * list_del_init(&child->list); * parent->num_children--; */ #define list_del_init(n) list_del_init_(n, LIST_LOC) static inline void list_del_init_(struct list_node *n, const char *abortstr) { list_del_(n, abortstr); list_node_init(n); } /** * list_del_from - delete an entry from a known linked list. * @h: the list_head the node is in. * @n: the list_node to delete from the list. * * This explicitly indicates which list a node is expected to be in, * which is better documentation and can catch more bugs. * * See also: list_del() * * Example: * list_del_from(&parent->children, &child->list); * parent->num_children--; */ static inline void list_del_from(struct list_head *h, struct list_node *n) { #ifdef CCAN_LIST_DEBUG { /* Thorough check: make sure it was in list! */ struct list_node *i; for (i = h->n.next; i != n; i = i->next) assert(i != &h->n); } #endif /* CCAN_LIST_DEBUG */ /* Quick test that catches a surprising number of bugs. */ assert(!list_empty(h)); list_del(n); } /** * list_swap - swap out an entry from an (unknown) linked list for a new one. * @o: the list_node to replace from the list. * @n: the list_node to insert in place of the old one. * * Note that this leaves @o in an undefined state; it can be added to * another list, but not deleted/swapped again. * * See also: * list_del() * * Example: * struct child x1, x2; * LIST_HEAD(xh); * * list_add(&xh, &x1.list); * list_swap(&x1.list, &x2.list); */ #define list_swap(o, n) list_swap_(o, n, LIST_LOC) static inline void list_swap_(struct list_node *o, struct list_node *n, const char* abortstr) { (void)list_debug_node(o, abortstr); *n = *o; n->next->prev = n; n->prev->next = n; #ifdef CCAN_LIST_DEBUG /* Catch use-after-del. */ o->next = o->prev = NULL; #endif } /** * list_entry - convert a list_node back into the structure containing it. * @n: the list_node * @type: the type of the entry * @member: the list_node member of the type * * Example: * // First list entry is children.next; convert back to child. * child = list_entry(parent->children.n.next, struct child, list); * * See Also: * list_top(), list_for_each() */ #define list_entry(n, type, member) container_of(n, type, member) /** * list_top - get the first entry in a list * @h: the list_head * @type: the type of the entry * @member: the list_node member of the type * * If the list is empty, returns NULL. * * Example: * struct child *first; * first = list_top(&parent->children, struct child, list); * if (!first) * printf("Empty list!\n"); */ #define list_top(h, type, member) \ ((type *)list_top_((h), list_off_(type, member))) static inline const void *list_top_(const struct list_head *h, size_t off) { if (list_empty(h)) return NULL; return (const char *)h->n.next - off; } /** * list_pop - remove the first entry in a list * @h: the list_head * @type: the type of the entry * @member: the list_node member of the type * * If the list is empty, returns NULL. * * Example: * struct child *one; * one = list_pop(&parent->children, struct child, list); * if (!one) * printf("Empty list!\n"); */ #define list_pop(h, type, member) \ ((type *)list_pop_((h), list_off_(type, member))) static inline const void *list_pop_(const struct list_head *h, size_t off) { struct list_node *n; if (list_empty(h)) return NULL; n = h->n.next; list_del(n); return (const char *)n - off; } /** * list_tail - get the last entry in a list * @h: the list_head * @type: the type of the entry * @member: the list_node member of the type * * If the list is empty, returns NULL. * * Example: * struct child *last; * last = list_tail(&parent->children, struct child, list); * if (!last) * printf("Empty list!\n"); */ #define list_tail(h, type, member) \ ((type *)list_tail_((h), list_off_(type, member))) static inline const void *list_tail_(const struct list_head *h, size_t off) { if (list_empty(h)) return NULL; return (const char *)h->n.prev - off; } /** * list_for_each - iterate through a list. * @h: the list_head (warning: evaluated multiple times!) * @i: the structure containing the list_node * @member: the list_node member of the structure * * This is a convenient wrapper to iterate @i over the entire list. It's * a for loop, so you can break and continue as normal. * * Example: * list_for_each(&parent->children, child, list) * printf("Name: %s\n", child->name); */ #define list_for_each(h, i, member) \ list_for_each_off(h, i, list_off_var_(i, member)) /** * list_for_each_rev - iterate through a list backwards. * @h: the list_head * @i: the structure containing the list_node * @member: the list_node member of the structure * * This is a convenient wrapper to iterate @i over the entire list. It's * a for loop, so you can break and continue as normal. * * Example: * list_for_each_rev(&parent->children, child, list) * printf("Name: %s\n", child->name); */ #define list_for_each_rev(h, i, member) \ list_for_each_rev_off(h, i, list_off_var_(i, member)) /** * list_for_each_rev_safe - iterate through a list backwards, * maybe during deletion * @h: the list_head * @i: the structure containing the list_node * @nxt: the structure containing the list_node * @member: the list_node member of the structure * * This is a convenient wrapper to iterate @i over the entire list backwards. * It's a for loop, so you can break and continue as normal. The extra * variable * @nxt is used to hold the next element, so you can delete @i * from the list. * * Example: * struct child *next; * list_for_each_rev_safe(&parent->children, child, next, list) { * printf("Name: %s\n", child->name); * } */ #define list_for_each_rev_safe(h, i, nxt, member) \ list_for_each_rev_safe_off(h, i, nxt, list_off_var_(i, member)) /** * list_for_each_safe - iterate through a list, maybe during deletion * @h: the list_head * @i: the structure containing the list_node * @nxt: the structure containing the list_node * @member: the list_node member of the structure * * This is a convenient wrapper to iterate @i over the entire list. It's * a for loop, so you can break and continue as normal. The extra variable * @nxt is used to hold the next element, so you can delete @i from the list. * * Example: * list_for_each_safe(&parent->children, child, next, list) { * list_del(&child->list); * parent->num_children--; * } */ #define list_for_each_safe(h, i, nxt, member) \ list_for_each_safe_off(h, i, nxt, list_off_var_(i, member)) /** * list_next - get the next entry in a list * @h: the list_head * @i: a pointer to an entry in the list. * @member: the list_node member of the structure * * If @i was the last entry in the list, returns NULL. * * Example: * struct child *second; * second = list_next(&parent->children, first, list); * if (!second) * printf("No second child!\n"); */ #define list_next(h, i, member) \ ((list_typeof(i))list_entry_or_null(list_debug(h, \ __FILE__ ":" stringify(__LINE__)), \ (i)->member.next, \ list_off_var_((i), member))) /** * list_prev - get the previous entry in a list * @h: the list_head * @i: a pointer to an entry in the list. * @member: the list_node member of the structure * * If @i was the first entry in the list, returns NULL. * * Example: * first = list_prev(&parent->children, second, list); * if (!first) * printf("Can't go back to first child?!\n"); */ #define list_prev(h, i, member) \ ((list_typeof(i))list_entry_or_null(list_debug(h, \ __FILE__ ":" stringify(__LINE__)), \ (i)->member.prev, \ list_off_var_((i), member))) /** * list_append_list - empty one list onto the end of another. * @to: the list to append into * @from: the list to empty. * * This takes the entire contents of @from and moves it to the end of * @to. After this @from will be empty. * * Example: * struct list_head adopter; * * list_append_list(&adopter, &parent->children); * assert(list_empty(&parent->children)); * parent->num_children = 0; */ #define list_append_list(t, f) list_append_list_(t, f, \ __FILE__ ":" stringify(__LINE__)) static inline void list_append_list_(struct list_head *to, struct list_head *from, const char *abortstr) { struct list_node *from_tail = list_debug(from, abortstr)->n.prev; struct list_node *to_tail = list_debug(to, abortstr)->n.prev; /* Sew in head and entire list. */ to->n.prev = from_tail; from_tail->next = &to->n; to_tail->next = &from->n; from->n.prev = to_tail; /* Now remove head. */ list_del(&from->n); list_head_init(from); } /** * list_prepend_list - empty one list into the start of another. * @to: the list to prepend into * @from: the list to empty. * * This takes the entire contents of @from and moves it to the start * of @to. After this @from will be empty. * * Example: * list_prepend_list(&adopter, &parent->children); * assert(list_empty(&parent->children)); * parent->num_children = 0; */ #define list_prepend_list(t, f) list_prepend_list_(t, f, LIST_LOC) static inline void list_prepend_list_(struct list_head *to, struct list_head *from, const char *abortstr) { struct list_node *from_tail = list_debug(from, abortstr)->n.prev; struct list_node *to_head = list_debug(to, abortstr)->n.next; /* Sew in head and entire list. */ to->n.next = &from->n; from->n.prev = &to->n; to_head->prev = from_tail; from_tail->next = to_head; /* Now remove head. */ list_del(&from->n); list_head_init(from); } /* internal macros, do not use directly */ #define list_for_each_off_dir_(h, i, off, dir) \ for (i = list_node_to_off_(list_debug(h, LIST_LOC)->n.dir, \ (off)); \ list_node_from_off_((void *)i, (off)) != &(h)->n; \ i = list_node_to_off_(list_node_from_off_((void *)i, (off))->dir, \ (off))) #define list_for_each_safe_off_dir_(h, i, nxt, off, dir) \ for (i = list_node_to_off_(list_debug(h, LIST_LOC)->n.dir, \ (off)), \ nxt = list_node_to_off_(list_node_from_off_(i, (off))->dir, \ (off)); \ list_node_from_off_(i, (off)) != &(h)->n; \ i = nxt, \ nxt = list_node_to_off_(list_node_from_off_(i, (off))->dir, \ (off))) /** * list_for_each_off - iterate through a list of memory regions. * @h: the list_head * @i: the pointer to a memory region which contains list node data. * @off: offset(relative to @i) at which list node data resides. * * This is a low-level wrapper to iterate @i over the entire list, used to * implement all oher, more high-level, for-each constructs. It's a for loop, * so you can break and continue as normal. * * WARNING! Being the low-level macro that it is, this wrapper doesn't know * nor care about the type of @i. The only assumption made is that @i points * to a chunk of memory that at some @offset, relative to @i, contains a * properly filled `struct list_node' which in turn contains pointers to * memory chunks and it's turtles all the way down. With all that in mind * remember that given the wrong pointer/offset couple this macro will * happily churn all you memory until SEGFAULT stops it, in other words * caveat emptor. * * It is worth mentioning that one of legitimate use-cases for that wrapper * is operation on opaque types with known offset for `struct list_node' * member(preferably 0), because it allows you not to disclose the type of * @i. * * Example: * list_for_each_off(&parent->children, child, * offsetof(struct child, list)) * printf("Name: %s\n", child->name); */ #define list_for_each_off(h, i, off) \ list_for_each_off_dir_((h),(i),(off),next) /** * list_for_each_rev_off - iterate through a list of memory regions backwards * @h: the list_head * @i: the pointer to a memory region which contains list node data. * @off: offset(relative to @i) at which list node data resides. * * See list_for_each_off for details */ #define list_for_each_rev_off(h, i, off) \ list_for_each_off_dir_((h),(i),(off),prev) /** * list_for_each_safe_off - iterate through a list of memory regions, maybe * during deletion * @h: the list_head * @i: the pointer to a memory region which contains list node data. * @nxt: the structure containing the list_node * @off: offset(relative to @i) at which list node data resides. * * For details see `list_for_each_off' and `list_for_each_safe' * descriptions. * * Example: * list_for_each_safe_off(&parent->children, child, * next, offsetof(struct child, list)) * printf("Name: %s\n", child->name); */ #define list_for_each_safe_off(h, i, nxt, off) \ list_for_each_safe_off_dir_((h),(i),(nxt),(off),next) /** * list_for_each_rev_safe_off - iterate backwards through a list of * memory regions, maybe during deletion * @h: the list_head * @i: the pointer to a memory region which contains list node data. * @nxt: the structure containing the list_node * @off: offset(relative to @i) at which list node data resides. * * For details see `list_for_each_rev_off' and `list_for_each_rev_safe' * descriptions. * * Example: * list_for_each_rev_safe_off(&parent->children, child, * next, offsetof(struct child, list)) * printf("Name: %s\n", child->name); */ #define list_for_each_rev_safe_off(h, i, nxt, off) \ list_for_each_safe_off_dir_((h),(i),(nxt),(off),prev) /* Other -off variants. */ #define list_entry_off(n, type, off) \ ((type *)list_node_from_off_((n), (off))) #define list_head_off(h, type, off) \ ((type *)list_head_off((h), (off))) #define list_tail_off(h, type, off) \ ((type *)list_tail_((h), (off))) #define list_add_off(h, n, off) \ list_add((h), list_node_from_off_((n), (off))) #define list_del_off(n, off) \ list_del(list_node_from_off_((n), (off))) #define list_del_from_off(h, n, off) \ list_del_from(h, list_node_from_off_((n), (off))) /* Offset helper functions so we only single-evaluate. */ static inline void *list_node_to_off_(struct list_node *node, size_t off) { return (void *)((char *)node - off); } static inline struct list_node *list_node_from_off_(void *ptr, size_t off) { return (struct list_node *)((char *)ptr + off); } /* Get the offset of the member, but make sure it's a list_node. */ #define list_off_(type, member) \ (container_off(type, member) + \ check_type(((type *)0)->member, struct list_node)) #define list_off_var_(var, member) \ (container_off_var(var, member) + \ check_type(var->member, struct list_node)) #if HAVE_TYPEOF #define list_typeof(var) typeof(var) #else #define list_typeof(var) void * #endif /* Returns member, or NULL if at end of list. */ static inline void *list_entry_or_null(const struct list_head *h, const struct list_node *n, size_t off) { if (n == &h->n) return NULL; return (char *)n - off; } #endif /* CCAN_LIST_H */ ndctl-81/ccan/minmax/000077500000000000000000000000001476737544500146335ustar00rootroot00000000000000ndctl-81/ccan/minmax/minmax.h000066400000000000000000000023501476737544500162750ustar00rootroot00000000000000/* SPDX-License-Identifier: CC0-1.0 */ #ifndef CCAN_MINMAX_H #define CCAN_MINMAX_H #include "config.h" #include #if !HAVE_STATEMENT_EXPR || !HAVE_TYPEOF /* * Without these, there's no way to avoid unsafe double evaluation of * the arguments */ #error Sorry, minmax module requires statement expressions and typeof #endif #if HAVE_BUILTIN_TYPES_COMPATIBLE_P #define MINMAX_ASSERT_COMPATIBLE(a, b) \ BUILD_ASSERT(__builtin_types_compatible_p(a, b)) #else #define MINMAX_ASSERT_COMPATIBLE(a, b) \ do { } while (0) #endif #define min(a, b) \ ({ \ typeof(a) _a = (a); \ typeof(b) _b = (b); \ MINMAX_ASSERT_COMPATIBLE(typeof(_a), typeof(_b)); \ _a < _b ? _a : _b; \ }) #define max(a, b) \ ({ \ typeof(a) __a = (a); \ typeof(b) __b = (b); \ MINMAX_ASSERT_COMPATIBLE(typeof(__a), typeof(__b)); \ __a > __b ? __a : __b; \ }) #define clamp(v, f, c) (max(min((v), (c)), (f))) #define min_t(t, a, b) \ ({ \ t _ta = (a); \ t _tb = (b); \ min(_ta, _tb); \ }) #define max_t(t, a, b) \ ({ \ t _ta = (a); \ t _tb = (b); \ max(_ta, _tb); \ }) #define clamp_t(t, v, f, c) \ ({ \ t _tv = (v); \ t _tf = (f); \ t _tc = (c); \ clamp(_tv, _tf, _tc); \ }) #endif /* CCAN_MINMAX_H */ ndctl-81/ccan/short_types/000077500000000000000000000000001476737544500157255ustar00rootroot00000000000000ndctl-81/ccan/short_types/short_types.h000066400000000000000000000014071476737544500204630ustar00rootroot00000000000000/* SPDX-License-Identifier: CC0-1.0 */ #ifndef CCAN_SHORT_TYPES_H #define CCAN_SHORT_TYPES_H #include /** * u64/s64/u32/s32/u16/s16/u8/s8 - short names for explicitly-sized types. */ typedef uint64_t u64; typedef int64_t s64; typedef uint32_t u32; typedef int32_t s32; typedef uint16_t u16; typedef int16_t s16; typedef uint8_t u8; typedef int8_t s8; /* Whichever they include first, they get these definitions. */ #ifdef CCAN_ENDIAN_H /** * be64/be32/be16 - 64/32/16 bit big-endian representation. */ typedef beint64_t be64; typedef beint32_t be32; typedef beint16_t be16; /** * le64/le32/le16 - 64/32/16 bit little-endian representation. */ typedef leint64_t le64; typedef leint32_t le32; typedef leint16_t le16; #endif #endif /* CCAN_SHORT_TYPES_H */ ndctl-81/ccan/str/000077500000000000000000000000001476737544500141525ustar00rootroot00000000000000ndctl-81/ccan/str/debug.c000066400000000000000000000030551476737544500154070ustar00rootroot00000000000000// SPDX-License-Identifier: CC0-1.0 #include "config.h" #include #include #include #include #ifdef CCAN_STR_DEBUG /* Because we mug the real ones with macros, we need our own wrappers. */ int str_isalnum(int i) { assert(i >= -1 && i < 256); return isalnum(i); } int str_isalpha(int i) { assert(i >= -1 && i < 256); return isalpha(i); } int str_isascii(int i) { assert(i >= -1 && i < 256); return isascii(i); } #if HAVE_ISBLANK int str_isblank(int i) { assert(i >= -1 && i < 256); return isblank(i); } #endif int str_iscntrl(int i) { assert(i >= -1 && i < 256); return iscntrl(i); } int str_isdigit(int i) { assert(i >= -1 && i < 256); return isdigit(i); } int str_isgraph(int i) { assert(i >= -1 && i < 256); return isgraph(i); } int str_islower(int i) { assert(i >= -1 && i < 256); return islower(i); } int str_isprint(int i) { assert(i >= -1 && i < 256); return isprint(i); } int str_ispunct(int i) { assert(i >= -1 && i < 256); return ispunct(i); } int str_isspace(int i) { assert(i >= -1 && i < 256); return isspace(i); } int str_isupper(int i) { assert(i >= -1 && i < 256); return isupper(i); } int str_isxdigit(int i) { assert(i >= -1 && i < 256); return isxdigit(i); } #undef strstr #undef strchr #undef strrchr char *str_strstr(const char *haystack, const char *needle) { return strstr(haystack, needle); } char *str_strchr(const char *haystack, int c) { return strchr(haystack, c); } char *str_strrchr(const char *haystack, int c) { return strrchr(haystack, c); } #endif ndctl-81/ccan/str/str.c000066400000000000000000000004061476737544500151260ustar00rootroot00000000000000// SPDX-License-Identifier: CC0-1.0 #include size_t strcount(const char *haystack, const char *needle) { size_t i = 0, nlen = strlen(needle); while ((haystack = strstr(haystack, needle)) != NULL) { i++; haystack += nlen; } return i; } ndctl-81/ccan/str/str.h000066400000000000000000000134611476737544500151400ustar00rootroot00000000000000/* SPDX-License-Identifier: CC0-1.0 */ #ifndef CCAN_STR_H #define CCAN_STR_H #include "config.h" #include #include #include #include /** * streq - Are two strings equal? * @a: first string * @b: first string * * This macro is arguably more readable than "!strcmp(a, b)". * * Example: * if (streq(somestring, "")) * printf("String is empty!\n"); */ #define streq(a,b) (strcmp((a),(b)) == 0) /** * strstarts - Does this string start with this prefix? * @str: string to test * @prefix: prefix to look for at start of str * * Example: * if (strstarts(somestring, "foo")) * printf("String %s begins with 'foo'!\n", somestring); */ #define strstarts(str,prefix) (strncmp((str),(prefix),strlen(prefix)) == 0) /** * strends - Does this string end with this postfix? * @str: string to test * @postfix: postfix to look for at end of str * * Example: * if (strends(somestring, "foo")) * printf("String %s end with 'foo'!\n", somestring); */ static inline bool strends(const char *str, const char *postfix) { if (strlen(str) < strlen(postfix)) return false; return streq(str + strlen(str) - strlen(postfix), postfix); } /** * stringify - Turn expression into a string literal * @expr: any C expression * * Example: * #define PRINT_COND_IF_FALSE(cond) \ * ((cond) || printf("%s is false!", stringify(cond))) */ #define stringify(expr) stringify_1(expr) /* Double-indirection required to stringify expansions */ #define stringify_1(expr) #expr /** * strcount - Count number of (non-overlapping) occurrences of a substring. * @haystack: a C string * @needle: a substring * * Example: * assert(strcount("aaa aaa", "a") == 6); * assert(strcount("aaa aaa", "ab") == 0); * assert(strcount("aaa aaa", "aa") == 2); */ size_t strcount(const char *haystack, const char *needle); /** * STR_MAX_CHARS - Maximum possible size of numeric string for this type. * @type_or_expr: a pointer or integer type or expression. * * This provides enough space for a nul-terminated string which represents the * largest possible value for the type or expression. * * Note: The implementation adds extra space so hex values or negative * values will fit (eg. sprintf(... "%p"). ) * * Example: * char str[STR_MAX_CHARS(int)]; * * sprintf(str, "%i", 7); */ #define STR_MAX_CHARS(type_or_expr) \ ((sizeof(type_or_expr) * CHAR_BIT + 8) / 9 * 3 + 2 \ + STR_MAX_CHARS_TCHECK_(type_or_expr)) #if HAVE_TYPEOF /* Only a simple type can have 0 assigned, so test that. */ #define STR_MAX_CHARS_TCHECK_(type_or_expr) \ ({ typeof(type_or_expr) x = 0; (void)x; 0; }) #else #define STR_MAX_CHARS_TCHECK_(type_or_expr) 0 #endif /** * cisalnum - isalnum() which takes a char (and doesn't accept EOF) * @c: a character * * Surprisingly, the standard ctype.h isalnum() takes an int, which * must have the value of EOF (-1) or an unsigned char. This variant * takes a real char, and doesn't accept EOF. */ static inline bool cisalnum(char c) { return isalnum((unsigned char)c); } static inline bool cisalpha(char c) { return isalpha((unsigned char)c); } static inline bool cisascii(char c) { return isascii((unsigned char)c); } #if HAVE_ISBLANK static inline bool cisblank(char c) { return isblank((unsigned char)c); } #endif static inline bool ciscntrl(char c) { return iscntrl((unsigned char)c); } static inline bool cisdigit(char c) { return isdigit((unsigned char)c); } static inline bool cisgraph(char c) { return isgraph((unsigned char)c); } static inline bool cislower(char c) { return islower((unsigned char)c); } static inline bool cisprint(char c) { return isprint((unsigned char)c); } static inline bool cispunct(char c) { return ispunct((unsigned char)c); } static inline bool cisspace(char c) { return isspace((unsigned char)c); } static inline bool cisupper(char c) { return isupper((unsigned char)c); } static inline bool cisxdigit(char c) { return isxdigit((unsigned char)c); } #include /* These checks force things out of line, hence they are under DEBUG. */ #ifdef CCAN_STR_DEBUG #include /* These are commonly misused: they take -1 or an *unsigned* char value. */ #undef isalnum #undef isalpha #undef isascii #undef isblank #undef iscntrl #undef isdigit #undef isgraph #undef islower #undef isprint #undef ispunct #undef isspace #undef isupper #undef isxdigit /* You can use a char if char is unsigned. */ #if HAVE_BUILTIN_TYPES_COMPATIBLE_P && HAVE_TYPEOF #define str_check_arg_(i) \ ((i) + BUILD_ASSERT_OR_ZERO(!__builtin_types_compatible_p(typeof(i), \ char) \ || (char)255 > 0)) #else #define str_check_arg_(i) (i) #endif #define isalnum(i) str_isalnum(str_check_arg_(i)) #define isalpha(i) str_isalpha(str_check_arg_(i)) #define isascii(i) str_isascii(str_check_arg_(i)) #if HAVE_ISBLANK #define isblank(i) str_isblank(str_check_arg_(i)) #endif #define iscntrl(i) str_iscntrl(str_check_arg_(i)) #define isdigit(i) str_isdigit(str_check_arg_(i)) #define isgraph(i) str_isgraph(str_check_arg_(i)) #define islower(i) str_islower(str_check_arg_(i)) #define isprint(i) str_isprint(str_check_arg_(i)) #define ispunct(i) str_ispunct(str_check_arg_(i)) #define isspace(i) str_isspace(str_check_arg_(i)) #define isupper(i) str_isupper(str_check_arg_(i)) #define isxdigit(i) str_isxdigit(str_check_arg_(i)) #if HAVE_TYPEOF /* With GNU magic, we can make const-respecting standard string functions. */ #undef strstr #undef strchr #undef strrchr /* + 0 is needed to decay array into pointer. */ #define strstr(haystack, needle) \ ((typeof((haystack) + 0))str_strstr((haystack), (needle))) #define strchr(haystack, c) \ ((typeof((haystack) + 0))str_strchr((haystack), (c))) #define strrchr(haystack, c) \ ((typeof((haystack) + 0))str_strrchr((haystack), (c))) #endif #endif /* CCAN_STR_DEBUG */ #endif /* CCAN_STR_H */ ndctl-81/ccan/str/str_debug.h000066400000000000000000000013641476737544500163050ustar00rootroot00000000000000/* SPDX-License-Identifier: CC0-1.0 */ #ifndef CCAN_STR_DEBUG_H #define CCAN_STR_DEBUG_H /* #define CCAN_STR_DEBUG 1 */ #ifdef CCAN_STR_DEBUG /* Because we mug the real ones with macros, we need our own wrappers. */ int str_isalnum(int i); int str_isalpha(int i); int str_isascii(int i); #if HAVE_ISBLANK int str_isblank(int i); #endif int str_iscntrl(int i); int str_isdigit(int i); int str_isgraph(int i); int str_islower(int i); int str_isprint(int i); int str_ispunct(int i); int str_isspace(int i); int str_isupper(int i); int str_isxdigit(int i); char *str_strstr(const char *haystack, const char *needle); char *str_strchr(const char *s, int c); char *str_strrchr(const char *s, int c); #endif /* CCAN_STR_DEBUG */ #endif /* CCAN_STR_DEBUG_H */ ndctl-81/clean_config.sh000077500000000000000000000001111476737544500153750ustar00rootroot00000000000000#!/bin/bash git ls-files -o --exclude build | grep config.h\$ | xargs rm ndctl-81/config.h.meson000066400000000000000000000103031476737544500151710ustar00rootroot00000000000000/* Debug messages. */ #mesondefine ENABLE_DEBUG /* destructive functional tests support */ #mesondefine ENABLE_DESTRUCTIVE /* Documentation / man pages. */ #mesondefine ENABLE_DOCS /* Enable keyutils support */ #mesondefine ENABLE_KEYUTILS /* System logging. */ #mesondefine ENABLE_LOGGING /* ndctl test poison support */ #mesondefine ENABLE_POISON /* ndctl test support */ #mesondefine ENABLE_TEST /* cxl monitor support */ #mesondefine ENABLE_LIBTRACEFS /* Define to 1 if big-endian-arch */ #mesondefine HAVE_BIG_ENDIAN /* Define to 1 if you have the declaration of `BUS_MCEERR_AR', and to 0 if you don't. */ #mesondefine HAVE_DECL_BUS_MCEERR_AR /* Define to 1 if you have the declaration of `MAP_SHARED_VALIDATE', and to 0 if you don't. */ #mesondefine HAVE_DECL_MAP_SHARED_VALIDATE /* Define to 1 if you have the declaration of `MAP_SYNC', and to 0 if you don't. */ #mesondefine HAVE_DECL_MAP_SYNC /* Define to 1 if you have the header file. */ #mesondefine HAVE_DLFCN_H /* Define to 1 if you have the header file. */ #mesondefine HAVE_INTTYPES_H /* Define to 1 if you have the header file. */ #mesondefine HAVE_KEYUTILS_H /* Define to 1 if you have the header file. */ #mesondefine HAVE_LINUX_VERSION_H /* Define to 1 if little-endian-arch */ #mesondefine HAVE_LITTLE_ENDIAN /* Define to 1 if you have the header file. */ #mesondefine HAVE_MEMORY_H /* Define to 1 if you have the `secure_getenv' function. */ #mesondefine HAVE_SECURE_GETENV /* Define to 1 if you have statement expressions. */ #mesondefine HAVE_STATEMENT_EXPR /* Define to 1 if you have the header file. */ #mesondefine HAVE_STDINT_H /* Define to 1 if you have the header file. */ #mesondefine HAVE_STDLIB_H /* Define to 1 if you have the header file. */ #mesondefine HAVE_STRINGS_H /* Define to 1 if you have the header file. */ #mesondefine HAVE_STRING_H /* Define to 1 if you have the header file. */ #mesondefine HAVE_SYS_STAT_H /* Define to 1 if you have the header file. */ #mesondefine HAVE_SYS_TYPES_H /* Define to 1 if typeof works with your compiler. */ #mesondefine HAVE_TYPEOF /* Define to 1 if you have the header file. */ #mesondefine HAVE_UNISTD_H /* Define to 1 if using libuuid */ #mesondefine HAVE_UUID /* Define to 1 if you have the `__secure_getenv' function. */ #mesondefine HAVE___SECURE_GETENV /* Define to 1 if you have json_object_new_uint64 in json-c */ #mesondefine HAVE_JSON_U64 /* Define to the sub-directory where libtool stores uninstalled libraries. */ #mesondefine LT_OBJDIR /* Name of package */ #mesondefine PACKAGE /* Define to the address where bug reports for this package should be sent. */ #mesondefine PACKAGE_BUGREPORT /* Define to the full name of this package. */ #mesondefine PACKAGE_NAME /* Define to the full name and version of this package. */ #mesondefine PACKAGE_STRING /* Define to the one symbol short name of this package. */ #mesondefine PACKAGE_TARNAME /* Define to the home page for this package. */ #mesondefine PACKAGE_URL /* Define to the version of this package. */ #mesondefine PACKAGE_VERSION /* Define to 1 if you have the ANSI C header files. */ #mesondefine STDC_HEADERS /* Version number of package */ #mesondefine VERSION /* Number of bits in a file offset, on hosts where this is settable. */ #mesondefine _FILE_OFFSET_BITS /* Define for large files, on AIX-style hosts. */ #mesondefine _LARGE_FILES /* Define to 1 if on MINIX. */ #mesondefine _MINIX /* Define to 2 if the system does not provide POSIX.1 features except with this defined. */ #mesondefine _POSIX_1_SOURCE /* Define to 1 if you need to in order for `stat' and other things to work. */ #mesondefine _POSIX_SOURCE /* Define to __typeof__ if your compiler spells it that way. */ #mesondefine typeof /* Define to enable GNU Source Extensions */ #mesondefine _GNU_SOURCE /* Locations to install configuration files, key config, man pages, etc.. */ #mesondefine NDCTL_CONF_FILE #mesondefine NDCTL_CONF_DIR #mesondefine DAXCTL_CONF_DIR #mesondefine NDCTL_KEYS_DIR #mesondefine NDCTL_MAN_PATH #mesondefine DAXCTL_MODPROBE_DATA #mesondefine DAXCTL_MODPROBE_INSTALL #mesondefine PREFIX ndctl-81/contrib/000077500000000000000000000000001476737544500140765ustar00rootroot00000000000000ndctl-81/contrib/meson.build000066400000000000000000000020211476737544500162330ustar00rootroot00000000000000bashcompletiondir = get_option('bashcompletiondir') if bashcompletiondir == '' bash_completion = dependency('bash-completion', required : false) if bash_completion.found() bashcompletiondir = bash_completion.get_pkgconfig_variable('completionsdir') else bashcompletiondir = datadir / 'bash-completion/completions' endif endif if bashcompletiondir != 'no' install_data('ndctl', install_dir : bashcompletiondir) # TODO Switch to symlinks once 0.61.0 is more widely available # install_symlink('daxctl', # install_dir : bashcompletiondir, # pointing_to : 'ndctl' # ) # install_symlink('cxl', # install_dir : bashcompletiondir, # pointing_to : 'ndctl' # ) install_data('ndctl', rename : 'daxctl', install_dir : bashcompletiondir) install_data('ndctl', rename : 'cxl', install_dir : bashcompletiondir) endif modprobedatadir = get_option('modprobedatadir') if modprobedatadir == '' modprobedatadir = sysconfdir + '/modprobe.d/' endif install_data('nvdimm-security.conf', install_dir : modprobedatadir) ndctl-81/contrib/ndctl000066400000000000000000000355331476737544500151360ustar00rootroot00000000000000# ndctl bash and zsh completion # Taken from perf's completion script. ### common helpers between ndctl and daxctl __my_reassemble_comp_words_by_ref() { local exclude i j first # Which word separators to exclude? exclude="${1//[^$COMP_WORDBREAKS]}" cword_=$COMP_CWORD if [ -z "$exclude" ]; then words_=("${COMP_WORDS[@]}") return fi # List of word completion separators has shrunk; # re-assemble words to complete. for ((i=0, j=0; i < ${#COMP_WORDS[@]}; i++, j++)); do # Append each nonempty word consisting of just # word separator characters to the current word. first=t while [ $i -gt 0 ] && [ -n "${COMP_WORDS[$i]}" ] && # word consists of excluded word separators [ "${COMP_WORDS[$i]//[^$exclude]}" = "${COMP_WORDS[$i]}" ] do # Attach to the previous token, # unless the previous token is the command name. if [ $j -ge 2 ] && [ -n "$first" ]; then ((j--)) fi first= words_[$j]=${words_[j]}${COMP_WORDS[i]} if [ $i = $COMP_CWORD ]; then cword_=$j fi if (($i < ${#COMP_WORDS[@]} - 1)); then ((i++)) else # Done. return fi done words_[$j]=${words_[j]}${COMP_WORDS[i]} if [ $i = $COMP_CWORD ]; then cword_=$j fi done } # Define preload_get_comp_words_by_ref="false", if the function # __nd_common_get_comp_words_by_ref() is required instead. preload_get_comp_words_by_ref="true" if [ $preload_get_comp_words_by_ref = "true" ]; then type _get_comp_words_by_ref &>/dev/null || preload_get_comp_words_by_ref="false" fi [ $preload_get_comp_words_by_ref = "true" ] || __nd_common_get_comp_words_by_ref() { local exclude cur_ words_ cword_ if [ "$1" = "-n" ]; then exclude=$2 shift 2 fi __my_reassemble_comp_words_by_ref "$exclude" cur_=${words_[cword_]} while [ $# -gt 0 ]; do case "$1" in cur) cur=$cur_ ;; prev) prev=${words_[$cword_-1]} ;; words) words=("${words_[@]}") ;; cword) cword=$cword_ ;; esac shift done } __nd_common_prev_skip_opts () { local i cmd_ cmds_ let i=cword-1 cmds_=$($cmd $1 --list-cmds) prev_skip_opts=() while [ $i -ge 0 ]; do if [[ ${words[i]} == $1 ]]; then return fi for cmd_ in $cmds_; do if [[ ${words[i]} == $cmd_ ]]; then prev_skip_opts=${words[i]} return fi done ((i--)) done } ### ndctl __ndctlcomp() { local i=0 COMPREPLY=( $( compgen -W "$1" -- "$2" ) ) for cword in "${COMPREPLY[@]}"; do if [[ "$cword" == @(--bus|--region|--type|--mode|--size|--dimm|--reconfig|--uuid|--name|--sector-size|--map|--namespace|--input|--output|--label-version|--align|--block|--count|--firmware|--media-temperature|--ctrl-temperature|--spares|--media-temperature-threshold|--ctrl-temperature-threshold|--spares-threshold|--media-temperature-alarm|--ctrl-temperature-alarm|--spares-alarm|--numa-node|--log|--dimm-event|--config-file|--key-handle|--key-path|--tpm-handle) ]]; then COMPREPLY[$i]="${cword}=" else COMPREPLY[$i]="${cword} " fi ((i++)) done } __ndctl_get_buses() { local opts="--buses $*" [ -n "$dimm_filter" ] && opts="$opts --dimm=$dimm_filter" [ -n "$reg_filter" ] && opts="$opts --region=$reg_filter" [ -n "$ns_filter" ] && opts="$opts --namespace=$ns_filter" echo "$(ndctl list $opts | grep -E "^\s*\"provider\":" | cut -d\" -f4)" } __ndctl_get_regions() { local opts="--regions $*" [ -n "$bus_filter" ] && opts="$opts --bus=$bus_filter" [ -n "$dimm_filter" ] && opts="$opts --dimm=$dimm_filter" [ -n "$ns_filter" ] && opts="$opts --namespace=$ns_filter" [ -n "$type_filter" ] && opts="$opts --type=$type_filter" [ -n "$idle_filter" ] && opts="$opts --idle" echo "$(ndctl list $opts | grep -E "^\s*\"dev\":" | cut -d\" -f4)" } __ndctl_get_ns() { opts="--namespaces $*" [ -n "$bus_filter" ] && opts="$opts --bus=$bus_filter" [ -n "$dimm_filter" ] && opts="$opts --dimm=$dimm_filter" [ -n "$reg_filter" ] && opts="$opts --region=$reg_filter" [ -n "$mode_filter" ] && opts="$opts --mode=$mode_filter" [ -n "$type_filter" ] && opts="$opts --type=$type_filter" [ -n "$idle_filter" ] && opts="$opts --idle" echo "$(ndctl list $opts | grep -E "^\s*\"dev\":" | cut -d\" -f4)" } __ndctl_get_dimms() { opts="--dimms $*" [ -n "$bus_filter" ] && opts="$opts --bus=$bus_filter" [ -n "$reg_filter" ] && opts="$opts --region=$reg_filter" [ -n "$ns_filter" ] && opts="$opts --namespace=$ns_filter" echo "$(ndctl list $opts | grep -E "^\s*\"dev\":" | cut -d\" -f4)" } __ndctl_get_sector_sizes() { if [[ "$mode_filter" == @(raw|memory|fsdax) ]]; then echo "512 4096" elif [[ "$mode_filter" == @(dax|devdax) ]]; then return else echo "512 520 528 4096 4104 4160 4224" fi } __ndctl_get_nodes() { local nlist="" for node in /sys/devices/system/node/node*; do node="$(basename $node)" if [[ $node =~ node([0-9]+) ]]; then nlist="$nlist ${BASH_REMATCH[1]}" else continue fi done echo "$nlist" } saveopts="" __ndctl_file_comp() { local cur="$1" _filedir saveopts="${COMPREPLY[*]}" } __ndctl_comp_options() { local cur=$1 local opts if [[ "$cur" == *=* ]]; then local cur_subopt=${cur%%=*} local cur_arg=${cur##*=} case $cur_subopt in --bus) opts=$(__ndctl_get_buses) ;; --region) opts=$(__ndctl_get_regions) ;; --dimm) opts=$(__ndctl_get_dimms -i) ;; --namespace) opts=$(__ndctl_get_ns) ;; --reconfig) # It is ok to reconfig disabled namespaces opts=$(__ndctl_get_ns -i) ;; --type) opts="pmem blk" if [[ "$mode_filter" == @(dax|memory|fsdax|devdax) ]]; then opts="pmem" fi ;; --mode) opts="raw sector fsdax devdax" if [[ "$type_filter" == "blk" ]]; then opts="raw sector" fi ;; --sector-size) opts=$(__ndctl_get_sector_sizes) ;; --map) opts="mem dev" ;; --output) ;& --input) ;& --firmware) __ndctl_file_comp "$cur_arg" return ;; --label-version) opts="1.1 1.2" ;; --media-temperature-alarm) ;& --ctrl-temperature-alarm) ;& --spares-alarm) opts="on off" ;; --numa-node) opts=$(__ndctl_get_nodes) ;; --log) local my_args=( "standard" "syslog" ) __ndctl_file_comp "$cur_arg" if [ -n "$cur_arg" ]; then # if $cur_arg is a substring of $my_args (as # determined by compgen), then we continue to # manually construct opts using $my_args, but # if it is not a substring (compgen returns # empty), then we defer to _ndctl_file_comp # and return immediately. (NOTE: subscripting # of the my_args array here is fragile). # If this pattern repeats, where we need to # complete with filenames and some custom # options, find a better way to generalize this if [ -z "$(compgen -W "${my_args[*]}" -- "$cur_arg")" ]; then return fi fi opts="${my_args[*]} $saveopts" ;; --dimm-event) opts="all \ dimm-spares-remaining \ dimm-media-temperature \ dimm-controller-temperature \ dimm-health-state \ dimm-unclean-shutdown \ " ;; --config-file) __ndctl_file_comp "$cur_arg" return ;; --key-path) __ndctl_file_comp "$cur_arg" return ;; *) return ;; esac __ndctlcomp "$opts" "$cur_arg" fi } __ndctl_comp_non_option_args() { local subcmd=$1 local cur=$2 local opts case $subcmd in enable-namespace) opts="$(__ndctl_get_ns -i) all" ;; disable-namespace) ;& destroy-namespace) opts="$(__ndctl_get_ns -i) all" ;; check-namespace) opts="$(__ndctl_get_ns -i) all" ;; clear-errors) opts="$(__ndctl_get_ns) all" ;; enable-region) opts="$(__ndctl_get_regions -i) all" ;; disable-region) opts="$(__ndctl_get_regions) all" ;; enable-dimm) opts="$(__ndctl_get_dimms -i) all" ;; disable-dimm) opts="$(__ndctl_get_dimms) all" ;; init-labels) ;& check-labels) ;& read-labels) ;& write-labels) ;& zero-labels) opts="$(__ndctl_get_dimms -i) all" ;; inject-error) opts="$(__ndctl_get_ns -i)" ;; inject-smart) opts="$(__ndctl_get_dimms -i)" ;; wait-scrub) ;& start-scrub) opts="$(__ndctl_get_buses) all" ;; setup-passphrase) ;& update-passphrase) ;& remove-passphrase) ;& freeze-security) ;& sanitize-dimm) ;& wait-overwrite) opts="$(__ndctl_get_dimms -i) all" ;; *) return ;; esac __ndctlcomp "$opts" "$cur" } __ndctl_add_special_opts() { local subcmd=$1 case $subcmd in create-namespace) opts="$opts --no-autolabel" ;; esac } __ndctl_init_filters() { bus_filter='' reg_filter='' ns_filter='' dimm_filter='' type_filter='' idle_filter='' mode_filter='' for __word in "${words[@]}"; do if [[ "$__word" =~ --bus=(.*) ]]; then bus_filter="${BASH_REMATCH[1]}" # lets make sure this is in the list of buses local buses=$(__ndctl_get_buses) [[ "$buses" == *"$bus_filter"* ]] || bus_filter='' fi if [[ "$__word" =~ --region=(.*) ]]; then reg_filter="${BASH_REMATCH[1]}" # lets make sure this is in the list of regions local regions=$(__ndctl_get_regions -i) [[ "$regions" == *"$reg_filter"* ]] || reg_filter='' fi if [[ "$__word" =~ --namespace=(.*) ]]; then ns_filter="${BASH_REMATCH[1]}" # lets make sure this is in the list of namespaces local nss=$(__ndctl_get_ns -i) [[ "$nss" == *"$ns_filter"* ]] || ns_filter='' fi if [[ "$__word" =~ --dimm=(.*) ]]; then dimm_filter="${BASH_REMATCH[1]}" # lets make sure this is in the list of dimms local dimms=$(__ndctl_get_dimms) [[ "$dimms" == *"$dimm_filter"* ]] || dimm_filter='' fi if [[ "$__word" =~ --idle ]]; then idle_filter="1" fi if [[ "$__word" =~ --type=(.*) ]]; then type_filter="${BASH_REMATCH[1]}" fi if [[ "$__word" =~ --mode=(.*) ]]; then mode_filter="${BASH_REMATCH[1]}" fi done } __ndctl_main() { local cmd subcmd cmd=${words[0]} COMPREPLY=() __ndctl_init_filters # Skip options backward and find the last ndctl command __nd_common_prev_skip_opts subcmd=$prev_skip_opts # List ndctl subcommands or long options if [ -z $subcmd ]; then if [[ $cur == --* ]]; then cmds="--version --help --list-cmds" else cmds=$($cmd --list-cmds) fi __ndctlcomp "$cmds" "$cur" else # List long option names if [[ $cur == --* ]]; then opts=$($cmd $subcmd --list-opts) __ndctl_add_special_opts "$subcmd" __ndctlcomp "$opts" "$cur" __ndctl_comp_options "$cur" else [ -z "$subcmd" ] && return __ndctl_comp_non_option_args "$subcmd" "$cur" fi fi } if [[ -n ${ZSH_VERSION-} ]]; then autoload -U +X compinit && compinit __ndctlcomp() { emulate -L zsh local c IFS=$' \t\n' local -a array for c in ${=1}; do case $c in --*=*|*.) ;; *) c="$c " ;; esac array[${#array[@]}+1]="$c" done compset -P '*[=:]' compadd -Q -S '' -a -- array && _ret=0 } _ndctl() { local _ret=1 cur cword prev cur=${words[CURRENT]} prev=${words[CURRENT-1]} let cword=CURRENT-1 emulate ksh -c __ndctl_main let _ret && _default && _ret=0 return _ret } compdef _ndctl ndctl return fi type ndctl &>/dev/null && _ndctl() { local cur words cword prev if [ $preload_get_comp_words_by_ref = "true" ]; then _get_comp_words_by_ref -n =: cur words cword prev else __nd_common_get_comp_words_by_ref -n =: cur words cword prev fi __ndctl_main } ### daxctl ### __daxctl_get_devs() { local opts=("--devices" "$*") daxctl list "${opts[@]}" | grep -E "^\s*\"chardev\":" | cut -d'"' -f4 } __daxctl_get_regions() { local opts=("--regions" "$*") daxctl list "${opts[@]}" | grep -E "^\s*\"id\":" | grep -Eo "[0-9]+" } __daxctlcomp() { local i=0 COMPREPLY=( $( compgen -W "$1" -- "$2" ) ) for cword in "${COMPREPLY[@]}"; do if [[ "$cword" == @(--region|--dev|--mode) ]]; then COMPREPLY[$i]="${cword}=" else COMPREPLY[$i]="${cword} " fi ((i++)) done } __daxctl_comp_options() { local cur=$1 local opts if [[ "$cur" == *=* ]]; then local cur_subopt=${cur%%=*} local cur_arg=${cur##*=} case $cur_subopt in --region) opts="$(__daxctl_get_regions -i)" ;; --dev) opts="$(__daxctl_get_devs -i)" ;; --mode) opts="system-ram devdax" ;; *) return ;; esac __daxctlcomp "$opts" "$cur_arg" fi } __daxctl_comp_non_option_args() { local subcmd=$1 local cur=$2 local opts case $subcmd in reconfigure-device) ;& online-memory) ;& offline-memory) opts="$(__daxctl_get_devs -i) all" ;; *) return ;; esac __daxctlcomp "$opts" "$cur" } __daxctl_main() { local cmd subcmd cmd=${words[0]} COMPREPLY=() # Skip options backward and find the last daxctl command __nd_common_prev_skip_opts subcmd=$prev_skip_opts # List daxctl subcommands or long options if [ -z $subcmd ]; then if [[ $cur == --* ]]; then cmds="--version --help --list-cmds" else cmds=$($cmd --list-cmds) fi __daxctlcomp "$cmds" "$cur" else # List long option names if [[ $cur == --* ]]; then opts=$($cmd $subcmd --list-opts) __daxctlcomp "$opts" "$cur" __daxctl_comp_options "$cur" else [ -z "$subcmd" ] && return __daxctl_comp_non_option_args "$subcmd" "$cur" fi fi } type daxctl &>/dev/null && _daxctl() { local cur words cword prev if [ $preload_get_comp_words_by_ref = "true" ]; then _get_comp_words_by_ref -n =: cur words cword prev else __nd_common_get_comp_words_by_ref -n =: cur words cword prev fi __daxctl_main } ### cxl-cli ### __cxl_get_devs() { local opts=("--memdevs" "$*") cxl list "${opts[@]}" | grep -E "^\s*\"memdev\":" | cut -d'"' -f4 } __cxlcomp() { local i=0 COMPREPLY=( $( compgen -W "$1" -- "$2" ) ) for cword in "${COMPREPLY[@]}"; do if [[ "$cword" == @(--memdev|--offset|--size|--input|--output) ]]; then COMPREPLY[$i]="${cword}=" else COMPREPLY[$i]="${cword} " fi ((i++)) done } __cxl_comp_options() { local cur=$1 local opts if [[ "$cur" == *=* ]]; then local cur_subopt=${cur%%=*} local cur_arg=${cur##*=} case $cur_subopt in --memdev) opts="$(__cxl_get_devs -i)" ;; *) return ;; esac __cxlcomp "$opts" "$cur_arg" fi } __cxl_comp_non_option_args() { local subcmd=$1 local cur=$2 local opts case $subcmd in read-labels) ;& write-labels) ;& zero-labels) opts="$(__cxl_get_devs -i) all" ;; *) return ;; esac __cxlcomp "$opts" "$cur" } __cxl_main() { local cmd subcmd cmd=${words[0]} COMPREPLY=() # Skip options backward and find the last cxl command __nd_common_prev_skip_opts subcmd=$prev_skip_opts # List cxl subcommands or long options if [ -z $subcmd ]; then if [[ $cur == --* ]]; then cmds="--version --help --list-cmds" else cmds=$($cmd --list-cmds) fi __cxlcomp "$cmds" "$cur" else # List long option names if [[ $cur == --* ]]; then opts=$($cmd $subcmd --list-opts) __cxlcomp "$opts" "$cur" __cxl_comp_options "$cur" else [ -z "$subcmd" ] && return __cxl_comp_non_option_args "$subcmd" "$cur" fi fi } type cxl &>/dev/null && _cxl() { local cur words cword prev if [ $preload_get_comp_words_by_ref = "true" ]; then _get_comp_words_by_ref -n =: cur words cword prev else __nd_common_get_comp_words_by_ref -n =: cur words cword prev fi __cxl_main } complete -o nospace -F _ndctl ndctl complete -o nospace -F _daxctl daxctl complete -o nospace -F _cxl cxl ndctl-81/contrib/nfit_test_depmod.conf000066400000000000000000000004611476737544500202750ustar00rootroot00000000000000# For nfit_test module overriding - copy to /etc/depmod.d or so override nfit * extra override device_dax * extra override dax_pmem * extra override dax_pmem_core * extra override dax_pmem_compat * extra override libnvdimm * extra override nd_btt * extra override nd_e820 * extra override nd_pmem * extra ndctl-81/contrib/nvdimm-security.conf000066400000000000000000000001451476737544500201040ustar00rootroot00000000000000install libnvdimm /usr/bin/ndctl load-keys ; /sbin/modprobe --ignore-install libnvdimm $CMDLINE_OPTS ndctl-81/cxl/000077500000000000000000000000001476737544500132245ustar00rootroot00000000000000ndctl-81/cxl/builtin.h000066400000000000000000000037271476737544500150540ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (C) 2020-2021 Intel Corporation. All rights reserved. */ #ifndef _CXL_BUILTIN_H_ #define _CXL_BUILTIN_H_ struct cxl_ctx; int cmd_list(int argc, const char **argv, struct cxl_ctx *ctx); int cmd_write_labels(int argc, const char **argv, struct cxl_ctx *ctx); int cmd_read_labels(int argc, const char **argv, struct cxl_ctx *ctx); int cmd_zero_labels(int argc, const char **argv, struct cxl_ctx *ctx); int cmd_init_labels(int argc, const char **argv, struct cxl_ctx *ctx); int cmd_check_labels(int argc, const char **argv, struct cxl_ctx *ctx); int cmd_disable_memdev(int argc, const char **argv, struct cxl_ctx *ctx); int cmd_enable_memdev(int argc, const char **argv, struct cxl_ctx *ctx); int cmd_reserve_dpa(int argc, const char **argv, struct cxl_ctx *ctx); int cmd_free_dpa(int argc, const char **argv, struct cxl_ctx *ctx); int cmd_update_fw(int argc, const char **argv, struct cxl_ctx *ctx); int cmd_set_alert_config(int argc, const char **argv, struct cxl_ctx *ctx); int cmd_wait_sanitize(int argc, const char **argv, struct cxl_ctx *ctx); int cmd_disable_port(int argc, const char **argv, struct cxl_ctx *ctx); int cmd_enable_port(int argc, const char **argv, struct cxl_ctx *ctx); int cmd_set_partition(int argc, const char **argv, struct cxl_ctx *ctx); int cmd_disable_bus(int argc, const char **argv, struct cxl_ctx *ctx); int cmd_create_region(int argc, const char **argv, struct cxl_ctx *ctx); int cmd_enable_region(int argc, const char **argv, struct cxl_ctx *ctx); int cmd_disable_region(int argc, const char **argv, struct cxl_ctx *ctx); int cmd_destroy_region(int argc, const char **argv, struct cxl_ctx *ctx); #ifdef ENABLE_LIBTRACEFS int cmd_monitor(int argc, const char **argv, struct cxl_ctx *ctx); #else static inline int cmd_monitor(int argc, const char **argv, struct cxl_ctx *ctx) { fprintf(stderr, "cxl monitor: unavailable, rebuild with '-Dlibtracefs=enabled'\n"); return EXIT_FAILURE; } #endif #endif /* _CXL_BUILTIN_H_ */ ndctl-81/cxl/bus.c000066400000000000000000000071611476737544500141660ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) 2020-2022 Intel Corporation. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include "filter.h" static struct parameters { bool debug; bool force; } param; static struct log_ctx bl; #define BASE_OPTIONS() \ OPT_BOOLEAN(0, "debug", ¶m.debug, "turn on debug") #define DISABLE_OPTIONS() \ OPT_BOOLEAN('f', "force", ¶m.force, \ "DANGEROUS: override active memdev safety checks") static const struct option disable_options[] = { BASE_OPTIONS(), DISABLE_OPTIONS(), OPT_END(), }; static int action_disable(struct cxl_bus *bus) { const char *devname = cxl_bus_get_devname(bus); struct cxl_ctx *ctx = cxl_bus_get_ctx(bus); struct cxl_memdev *memdev; int active_memdevs = 0; cxl_memdev_foreach(ctx, memdev) if (bus == cxl_memdev_get_bus(memdev)) active_memdevs++; if (active_memdevs && !param.force) { /* * TODO: actually detect rather than assume active just * because the memdev is enabled */ log_err(&bl, "%s hosts %d memdev%s which %s part of an active region\n", devname, active_memdevs, active_memdevs > 1 ? "s" : "", active_memdevs > 1 ? "are" : "is"); log_err(&bl, "See 'cxl list -M -b %s' to see impacted device%s\n", devname, active_memdevs > 1 ? "s" : ""); return -EBUSY; } return cxl_bus_disable_invalidate(bus); } static struct cxl_bus *find_cxl_bus(struct cxl_ctx *ctx, const char *ident) { struct cxl_bus *bus; cxl_bus_foreach(ctx, bus) if (util_cxl_bus_filter(bus, ident)) return bus; return NULL; } static int bus_action(int argc, const char **argv, struct cxl_ctx *ctx, int (*action)(struct cxl_bus *bus), const struct option *options, const char *usage) { int i, rc = 0, count = 0, err = 0; const char * const u[] = { usage, NULL }; unsigned long id; log_init(&bl, "cxl bus", "CXL_PORT_LOG"); argc = parse_options(argc, argv, options, u, 0); if (argc == 0) usage_with_options(u, options); for (i = 0; i < argc; i++) { if (strcmp(argv[i], "all") == 0) { argv[0] = "all"; argc = 1; break; } if (sscanf(argv[i], "root%lu", &id) == 1) continue; if (sscanf(argv[i], "%lu", &id) == 1) continue; log_err(&bl, "'%s' is not a valid bus identifer\n", argv[i]); err++; } if (err == argc) { usage_with_options(u, options); return -EINVAL; } if (param.debug) { cxl_set_log_priority(ctx, LOG_DEBUG); bl.log_priority = LOG_DEBUG; } else bl.log_priority = LOG_INFO; rc = 0; err = 0; count = 0; for (i = 0; i < argc; i++) { struct cxl_bus *bus; bus = find_cxl_bus(ctx, argv[i]); if (!bus) { log_dbg(&bl, "bus: %s not found\n", argv[i]); continue; } log_dbg(&bl, "run action on bus: %s\n", cxl_bus_get_devname(bus)); rc = action(bus); if (rc == 0) count++; else if (rc && !err) err = rc; } rc = err; /* * count if some actions succeeded, 0 if none were attempted, * negative error code otherwise. */ if (count > 0) return count; return rc; } int cmd_disable_bus(int argc, const char **argv, struct cxl_ctx *ctx) { int count = bus_action( argc, argv, ctx, action_disable, disable_options, "cxl disable-bus [..] []"); log_info(&bl, "disabled %d bus%s\n", count >= 0 ? count : 0, count > 1 ? "s" : ""); return count >= 0 ? 0 : EXIT_FAILURE; } ndctl-81/cxl/cxl-monitor.service000066400000000000000000000002021476737544500170530ustar00rootroot00000000000000[Unit] Description=CXL Monitor Daemon [Service] Type=simple ExecStart=/usr/bin/cxl monitor [Install] WantedBy=multi-user.target ndctl-81/cxl/cxl.c000066400000000000000000000062011476737544500141550ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) 2020-2021 Intel Corporation. All rights reserved. */ /* Copyright (C) 2005 Andreas Ericsson. All rights reserved. */ /* originally copied from perf and git */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include const char cxl_usage_string[] = "cxl [--version] [--help] COMMAND [ARGS]"; const char cxl_more_info_string[] = "See 'cxl help COMMAND' for more information on a specific command.\n" " cxl --list-cmds to see all available commands"; static int cmd_version(int argc, const char **argv, struct cxl_ctx *ctx) { printf("%s\n", VERSION); return 0; } static int cmd_help(int argc, const char **argv, struct cxl_ctx *ctx) { const char * const builtin_help_subcommands[] = { "list", NULL, }; struct option builtin_help_options[] = { OPT_END(), }; const char *builtin_help_usage[] = { "cxl help [command]", NULL }; argc = parse_options_subcommand(argc, argv, builtin_help_options, builtin_help_subcommands, builtin_help_usage, 0); if (!argv[0]) { printf("\n usage: %s\n\n", cxl_usage_string); printf("\n %s\n\n", cxl_more_info_string); return 0; } return help_show_man_page(argv[0], "cxl", "CXL_MAN_VIEWER"); } static struct cmd_struct commands[] = { { "version", .c_fn = cmd_version }, { "list", .c_fn = cmd_list }, { "help", .c_fn = cmd_help }, { "zero-labels", .c_fn = cmd_zero_labels }, { "read-labels", .c_fn = cmd_read_labels }, { "write-labels", .c_fn = cmd_write_labels }, { "disable-memdev", .c_fn = cmd_disable_memdev }, { "enable-memdev", .c_fn = cmd_enable_memdev }, { "reserve-dpa", .c_fn = cmd_reserve_dpa }, { "free-dpa", .c_fn = cmd_free_dpa }, { "update-firmware", .c_fn = cmd_update_fw }, { "set-alert-config", .c_fn = cmd_set_alert_config }, { "wait-sanitize", .c_fn = cmd_wait_sanitize }, { "disable-port", .c_fn = cmd_disable_port }, { "enable-port", .c_fn = cmd_enable_port }, { "set-partition", .c_fn = cmd_set_partition }, { "disable-bus", .c_fn = cmd_disable_bus }, { "create-region", .c_fn = cmd_create_region }, { "enable-region", .c_fn = cmd_enable_region }, { "disable-region", .c_fn = cmd_disable_region }, { "destroy-region", .c_fn = cmd_destroy_region }, { "monitor", .c_fn = cmd_monitor }, }; int main(int argc, const char **argv) { struct cxl_ctx *ctx; int rc; /* Look for flags.. */ argv++; argc--; main_handle_options(&argv, &argc, cxl_usage_string, commands, ARRAY_SIZE(commands)); if (argc > 0) { if (!prefixcmp(argv[0], "--")) argv[0] += 2; } else { /* The user didn't specify a command; give them help */ printf("\n usage: %s\n\n", cxl_usage_string); printf("\n %s\n\n", cxl_more_info_string); goto out; } rc = cxl_new(&ctx); if (rc) goto out; main_handle_internal_command(argc, argv, ctx, commands, ARRAY_SIZE(commands), PROG_CXL); cxl_unref(ctx); fprintf(stderr, "Unknown command: '%s'\n", argv[0]); out: return 1; } ndctl-81/cxl/cxl_mem.h000066400000000000000000000146471476737544500150350ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1 */ /* Copyright (C) 2020-2021, Intel Corporation. All rights reserved. */ /* * CXL IOCTLs for Memory Devices */ #ifndef _UAPI_CXL_MEM_H_ #define _UAPI_CXL_MEM_H_ #include #include #include #define __user /** * DOC: UAPI * * Not all of all commands that the driver supports are always available for use * by userspace. Userspace must check the results from the QUERY command in * order to determine the live set of commands. */ #define CXL_MEM_QUERY_COMMANDS _IOR(0xCE, 1, struct cxl_mem_query_commands) #define CXL_MEM_SEND_COMMAND _IOWR(0xCE, 2, struct cxl_send_command) #define CXL_CMDS \ ___C(INVALID, "Invalid Command"), \ ___C(IDENTIFY, "Identify Command"), \ ___C(RAW, "Raw device command"), \ ___C(GET_SUPPORTED_LOGS, "Get Supported Logs"), \ ___C(GET_FW_INFO, "Get FW Info"), \ ___C(GET_PARTITION_INFO, "Get Partition Information"), \ ___C(GET_LSA, "Get Label Storage Area"), \ ___C(GET_HEALTH_INFO, "Get Health Info"), \ ___C(GET_LOG, "Get Log"), \ ___C(SET_PARTITION_INFO, "Set Partition Information"), \ ___C(SET_LSA, "Set Label Storage Area"), \ ___C(GET_ALERT_CONFIG, "Get Alert Configuration"), \ ___C(SET_ALERT_CONFIG, "Set Alert Configuration"), \ ___C(GET_SHUTDOWN_STATE, "Get Shutdown State"), \ ___C(SET_SHUTDOWN_STATE, "Set Shutdown State"), \ ___C(GET_POISON, "Get Poison List"), \ ___C(INJECT_POISON, "Inject Poison"), \ ___C(CLEAR_POISON, "Clear Poison"), \ ___C(GET_SCAN_MEDIA_CAPS, "Get Scan Media Capabilities"), \ ___C(SCAN_MEDIA, "Scan Media"), \ ___C(GET_SCAN_MEDIA, "Get Scan Media Results"), \ ___C(MAX, "invalid / last command") #define ___C(a, b) CXL_MEM_COMMAND_ID_##a enum { CXL_CMDS }; #undef ___C #define ___C(a, b) { b } static const struct { const char *name; } cxl_command_names[] = { CXL_CMDS }; /* * Here's how this actually breaks out: * cxl_command_names[] = { * [CXL_MEM_COMMAND_ID_INVALID] = { "Invalid Command" }, * [CXL_MEM_COMMAND_ID_IDENTIFY] = { "Identify Command" }, * ... * [CXL_MEM_COMMAND_ID_MAX] = { "invalid / last command" }, * }; */ #undef ___C /** * struct cxl_command_info - Command information returned from a query. * @id: ID number for the command. * @flags: Flags that specify command behavior. * @size_in: Expected input size, or -1 if variable length. * @size_out: Expected output size, or -1 if variable length. * * Represents a single command that is supported by both the driver and the * hardware. This is returned as part of an array from the query ioctl. The * following would be a command that takes a variable length input and returns 0 * bytes of output. * * - @id = 10 * - @flags = 0 * - @size_in = -1 * - @size_out = 0 * * See struct cxl_mem_query_commands. */ struct cxl_command_info { __u32 id; __u32 flags; #define CXL_MEM_COMMAND_FLAG_MASK GENMASK(0, 0) __s32 size_in; __s32 size_out; }; /** * struct cxl_mem_query_commands - Query supported commands. * @n_commands: In/out parameter. When @n_commands is > 0, the driver will * return min(num_support_commands, n_commands). When @n_commands * is 0, driver will return the number of total supported commands. * @rsvd: Reserved for future use. * @commands: Output array of supported commands. This array must be allocated * by userspace to be at least min(num_support_commands, @n_commands) * * Allow userspace to query the available commands supported by both the driver, * and the hardware. Commands that aren't supported by either the driver, or the * hardware are not returned in the query. * * Examples: * * - { .n_commands = 0 } // Get number of supported commands * - { .n_commands = 15, .commands = buf } // Return first 15 (or less) * supported commands * * See struct cxl_command_info. */ struct cxl_mem_query_commands { /* * Input: Number of commands to return (space allocated by user) * Output: Number of commands supported by the driver/hardware * * If n_commands is 0, kernel will only return number of commands and * not try to populate commands[], thus allowing userspace to know how * much space to allocate */ __u32 n_commands; __u32 rsvd; struct cxl_command_info __user commands[]; /* out: supported commands */ }; /** * struct cxl_send_command - Send a command to a memory device. * @id: The command to send to the memory device. This must be one of the * commands returned by the query command. * @flags: Flags for the command (input). * @raw: Special fields for raw commands * @raw.opcode: Opcode passed to hardware when using the RAW command. * @raw.rsvd: Must be zero. * @rsvd: Must be zero. * @retval: Return value from the memory device (output). * @in: Parameters associated with input payload. * @in.size: Size of the payload to provide to the device (input). * @in.rsvd: Must be zero. * @in.payload: Pointer to memory for payload input, payload is little endian. * @out: Parameters associated with output payload. * @out.size: Size of the payload received from the device (input/output). This * field is filled in by userspace to let the driver know how much * space was allocated for output. It is populated by the driver to * let userspace know how large the output payload actually was. * @out.rsvd: Must be zero. * @out.payload: Pointer to memory for payload output, payload is little endian. * * Mechanism for userspace to send a command to the hardware for processing. The * driver will do basic validation on the command sizes. In some cases even the * payload may be introspected. Userspace is required to allocate large enough * buffers for size_out which can be variable length in certain situations. */ struct cxl_send_command { __u32 id; __u32 flags; union { struct { __u16 opcode; __u16 rsvd; } raw; __u32 rsvd; }; __u32 retval; struct { __s32 size; __u32 rsvd; __u64 payload; } in; struct { __s32 size; __u32 rsvd; __u64 payload; } out; }; #endif ndctl-81/cxl/filter.c000066400000000000000000000747021476737544500146670ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2015-2022 Intel Corporation. All rights reserved. #include #include #include #include #include #include #include #include #include "filter.h" #include "json.h" static const char *which_sep(const char *filter) { if (strchr(filter, ' ')) return " "; if (strchr(filter, ',')) return ","; return " "; } bool cxl_filter_has(const char *__filter, const char *needle) { char *filter, *save; const char *arg; if (!needle) return true; if (!__filter) return false; filter = strdup(__filter); if (!filter) return false; for (arg = strtok_r(filter, which_sep(__filter), &save); arg; arg = strtok_r(NULL, which_sep(__filter), &save)) if (strstr(arg, needle)) break; free(filter); if (arg) return true; return false; } struct cxl_endpoint *util_cxl_endpoint_filter(struct cxl_endpoint *endpoint, const char *__ident) { char *ident, *save; const char *arg; int endpoint_id; if (!__ident) return endpoint; ident = strdup(__ident); if (!ident) return NULL; for (arg = strtok_r(ident, which_sep(__ident), &save); arg; arg = strtok_r(NULL, which_sep(__ident), &save)) { if (strcmp(arg, "all") == 0) break; if ((sscanf(arg, "%d", &endpoint_id) == 1 || sscanf(arg, "endpoint%d", &endpoint_id) == 1) && cxl_endpoint_get_id(endpoint) == endpoint_id) break; if (strcmp(arg, cxl_endpoint_get_devname(endpoint)) == 0) break; if (strcmp(arg, cxl_endpoint_get_host(endpoint)) == 0) break; } free(ident); if (arg) return endpoint; return NULL; } static struct cxl_port *__util_cxl_port_filter(struct cxl_port *port, const char *__ident) { char *ident, *save; const char *arg; int port_id; if (!__ident) return port; ident = strdup(__ident); if (!ident) return NULL; for (arg = strtok_r(ident, which_sep(__ident), &save); arg; arg = strtok_r(NULL, which_sep(__ident), &save)) { if (strcmp(arg, "all") == 0) break; if (strcmp(arg, "root") == 0 && cxl_port_is_root(port)) break; if (strcmp(arg, "switch") == 0 && cxl_port_is_switch(port)) break; if (strcmp(arg, "endpoint") == 0 && cxl_port_is_endpoint(port)) break; if ((sscanf(arg, "%d", &port_id) == 1 || sscanf(arg, "port%d", &port_id) == 1) && cxl_port_get_id(port) == port_id) break; if (strcmp(arg, cxl_port_get_devname(port)) == 0) break; if (strcmp(arg, cxl_port_get_host(port)) == 0) break; } free(ident); if (arg) return port; return NULL; } static enum cxl_port_filter_mode pf_mode(struct cxl_filter_params *p) { if (p->single) return CXL_PF_SINGLE; return CXL_PF_ANCESTRY; } struct cxl_port *util_cxl_port_filter(struct cxl_port *port, const char *ident, enum cxl_port_filter_mode mode) { struct cxl_port *iter = port; while (iter) { if (__util_cxl_port_filter(iter, ident)) return port; if (mode == CXL_PF_SINGLE) return NULL; iter = cxl_port_get_parent(iter); } return NULL; } static struct cxl_endpoint * util_cxl_endpoint_filter_by_port(struct cxl_endpoint *endpoint, const char *ident, enum cxl_port_filter_mode mode) { struct cxl_port *iter = cxl_endpoint_get_port(endpoint); if (util_cxl_port_filter(iter, ident, CXL_PF_SINGLE)) return endpoint; iter = cxl_port_get_parent(iter); if (!iter) return NULL; if (util_cxl_port_filter(iter, ident, mode)) return endpoint; return NULL; } static struct cxl_decoder * util_cxl_decoder_filter_by_port(struct cxl_decoder *decoder, const char *ident, enum cxl_port_filter_mode mode) { struct cxl_port *port = cxl_decoder_get_port(decoder); if (util_cxl_port_filter(port, ident, mode)) return decoder; return NULL; } struct cxl_bus *util_cxl_bus_filter(struct cxl_bus *bus, const char *__ident) { char *ident, *save; const char *arg; int bus_id; if (!__ident) return bus; ident = strdup(__ident); if (!ident) return NULL; for (arg = strtok_r(ident, which_sep(__ident), &save); arg; arg = strtok_r(NULL, which_sep(__ident), &save)) { if (strcmp(arg, "all") == 0) break; if ((sscanf(arg, "%d", &bus_id) == 1 || sscanf(arg, "root%d", &bus_id) == 1) && cxl_bus_get_id(bus) == bus_id) break; if (strcmp(arg, cxl_bus_get_devname(bus)) == 0) break; if (strcmp(arg, cxl_bus_get_provider(bus)) == 0) break; } free(ident); if (arg) return bus; return NULL; } static struct cxl_port *util_cxl_port_filter_by_bus(struct cxl_port *port, const char *__ident) { struct cxl_ctx *ctx = cxl_port_get_ctx(port); struct cxl_bus *bus; if (!__ident) return port; if (cxl_port_is_root(port)) { bus = cxl_port_to_bus(port); bus = util_cxl_bus_filter(bus, __ident); return bus ? port : NULL; } cxl_bus_foreach(ctx, bus) { if (!util_cxl_bus_filter(bus, __ident)) continue; if (bus == cxl_port_get_bus(port)) return port; } return NULL; } struct cxl_memdev *util_cxl_memdev_filter_by_bus(struct cxl_memdev *memdev, const char *__ident) { struct cxl_ctx *ctx = cxl_memdev_get_ctx(memdev); struct cxl_bus *bus; if (!__ident) return memdev; cxl_bus_foreach(ctx, bus) { if (!util_cxl_bus_filter(bus, __ident)) continue; if (bus == cxl_memdev_get_bus(memdev)) return memdev; } return NULL; } static struct cxl_decoder * util_cxl_decoder_filter_by_bus(struct cxl_decoder *decoder, const char *__ident) { struct cxl_port *port = cxl_decoder_get_port(decoder); if (!util_cxl_port_filter_by_bus(port, __ident)) return NULL; return decoder; } static struct cxl_memdev * util_cxl_memdev_serial_filter(struct cxl_memdev *memdev, const char *__serials) { unsigned long long serial = 0; char *serials, *save, *end; const char *arg; if (!__serials) return memdev; serials = strdup(__serials); if (!serials) return NULL; for (arg = strtok_r(serials, which_sep(__serials), &save); arg; arg = strtok_r(NULL, which_sep(__serials), &save)) { serial = strtoull(arg, &end, 0); if (!arg[0] || end[0] != 0) continue; if (cxl_memdev_get_serial(memdev) == serial) break; } free(serials); if (arg) return memdev; return NULL; } struct cxl_memdev *util_cxl_memdev_filter(struct cxl_memdev *memdev, const char *__ident, const char *serials) { char *ident, *save; const char *name; int memdev_id; if (!__ident) return util_cxl_memdev_serial_filter(memdev, serials); ident = strdup(__ident); if (!ident) return NULL; for (name = strtok_r(ident, which_sep(__ident), &save); name; name = strtok_r(NULL, which_sep(__ident), &save)) { if (strcmp(name, "all") == 0) break; if ((sscanf(name, "%d", &memdev_id) == 1 || sscanf(name, "mem%d", &memdev_id) == 1) && cxl_memdev_get_id(memdev) == memdev_id) break; if (strcmp(name, cxl_memdev_get_devname(memdev)) == 0) break; if (strcmp(name, cxl_memdev_get_host(memdev)) == 0) break; } free(ident); if (name) return util_cxl_memdev_serial_filter(memdev, serials); return NULL; } static struct cxl_bus *util_cxl_bus_filter_by_memdev(struct cxl_bus *bus, const char *ident, const char *serial) { struct cxl_ctx *ctx = cxl_bus_get_ctx(bus); struct cxl_memdev *memdev; if (!ident && !serial) return bus; cxl_memdev_foreach(ctx, memdev) { if (!util_cxl_memdev_filter(memdev, ident, serial)) continue; if (cxl_memdev_get_bus(memdev) == bus) return bus; } return NULL; } static struct cxl_endpoint * util_cxl_endpoint_filter_by_memdev(struct cxl_endpoint *endpoint, const char *ident, const char *serial) { struct cxl_ctx *ctx = cxl_endpoint_get_ctx(endpoint); struct cxl_memdev *memdev; if (!ident && !serial) return endpoint; cxl_memdev_foreach(ctx, memdev) { if (!util_cxl_memdev_filter(memdev, ident, serial)) continue; if (cxl_memdev_get_endpoint(memdev) == endpoint) return endpoint; } return NULL; } struct cxl_port *util_cxl_port_filter_by_memdev(struct cxl_port *port, const char *ident, const char *serial) { struct cxl_ctx *ctx = cxl_port_get_ctx(port); struct cxl_memdev *memdev; if (!ident && !serial) return port; cxl_memdev_foreach(ctx, memdev) { if (!util_cxl_memdev_filter(memdev, ident, serial)) continue; if (cxl_port_hosts_memdev(port, memdev)) return port; } return NULL; } struct cxl_decoder *util_cxl_decoder_filter(struct cxl_decoder *decoder, const char *__ident) { struct cxl_port *port = cxl_decoder_get_port(decoder); int pid, did; char *ident, *save; const char *name; if (!__ident) return decoder; ident = strdup(__ident); if (!ident) return NULL; for (name = strtok_r(ident, which_sep(__ident), &save); name; name = strtok_r(NULL, which_sep(__ident), &save)) { if (strcmp(name, "all") == 0) break; if (strcmp(name, "root") == 0 && cxl_port_is_root(port)) break; if (strcmp(name, "switch") == 0 && cxl_port_is_switch(port)) break; if (strcmp(name, "endpoint") == 0 && cxl_port_is_endpoint(port)) break; if ((sscanf(name, "%d.%d", &pid, &did) == 2 || sscanf(name, "decoder%d.%d", &pid, &did) == 2) && cxl_port_get_id(port) == pid && cxl_decoder_get_id(decoder) == did) break; if (strcmp(name, cxl_decoder_get_devname(decoder)) == 0) break; } free(ident); if (name) return decoder; return NULL; } static struct cxl_decoder * util_cxl_decoder_filter_by_memdev(struct cxl_decoder *decoder, const char *ident, const char *serial) { struct cxl_ctx *ctx = cxl_decoder_get_ctx(decoder); struct cxl_endpoint *endpoint; struct cxl_memdev *memdev; struct cxl_port *port; if (!ident && !serial) return decoder; cxl_memdev_foreach(ctx, memdev) { if (!util_cxl_memdev_filter(memdev, ident, serial)) continue; if (cxl_decoder_get_target_by_memdev(decoder, memdev)) return decoder; port = cxl_decoder_get_port(decoder); if (!port || !cxl_port_is_endpoint(port)) continue; endpoint = cxl_port_to_endpoint(port); if (cxl_endpoint_get_memdev(endpoint) == memdev) return decoder; } return NULL; } struct cxl_target *util_cxl_target_filter_by_memdev(struct cxl_target *target, const char *ident, const char *serial) { struct cxl_decoder *decoder = cxl_target_get_decoder(target); struct cxl_ctx *ctx = cxl_decoder_get_ctx(decoder); struct cxl_memdev *memdev; if (!ident && !serial) return target; cxl_memdev_foreach(ctx, memdev) { if (!util_cxl_memdev_filter(memdev, ident, serial)) continue; if (cxl_target_maps_memdev(target, memdev)) return target; } return NULL; } struct cxl_dport *util_cxl_dport_filter_by_memdev(struct cxl_dport *dport, const char *ident, const char *serial) { struct cxl_port *port = cxl_dport_get_port(dport); struct cxl_ctx *ctx = cxl_port_get_ctx(port); struct cxl_memdev *memdev; if (!ident && !serial) return dport; cxl_memdev_foreach (ctx, memdev) { if (!util_cxl_memdev_filter(memdev, ident, serial)) continue; if (cxl_dport_maps_memdev(dport, memdev)) return dport; } return NULL; } static bool __memdev_filter_by_decoder(struct cxl_memdev *memdev, struct cxl_port *port, const char *ident) { struct cxl_decoder *decoder; struct cxl_endpoint *endpoint; cxl_decoder_foreach(port, decoder) { if (!util_cxl_decoder_filter(decoder, ident)) continue; if (cxl_decoder_get_target_by_memdev(decoder, memdev)) return true; } cxl_endpoint_foreach(port, endpoint) if (__memdev_filter_by_decoder( memdev, cxl_endpoint_get_port(endpoint), ident)) return true; return false; } static struct cxl_memdev * util_cxl_memdev_filter_by_decoder(struct cxl_memdev *memdev, const char *ident) { struct cxl_ctx *ctx = cxl_memdev_get_ctx(memdev); struct cxl_bus *bus; if (!ident) return memdev; cxl_bus_foreach(ctx, bus) { struct cxl_port *port, *top; port = cxl_bus_get_port(bus); if (__memdev_filter_by_decoder(memdev, port, ident)) return memdev; top = port; cxl_port_foreach_all(top, port) if (__memdev_filter_by_decoder(memdev, port, ident)) return memdev; } return NULL; } static bool __memdev_filter_by_port(struct cxl_memdev *memdev, struct cxl_port *port, const char *port_ident) { struct cxl_endpoint *endpoint; if (util_cxl_port_filter(port, port_ident, CXL_PF_SINGLE) && cxl_port_get_dport_by_memdev(port, memdev)) return true; cxl_endpoint_foreach(port, endpoint) if (__memdev_filter_by_port(memdev, cxl_endpoint_get_port(endpoint), port_ident)) return true; return false; } static struct cxl_memdev * util_cxl_memdev_filter_by_port(struct cxl_memdev *memdev, const char *bus_ident, const char *port_ident) { struct cxl_ctx *ctx = cxl_memdev_get_ctx(memdev); struct cxl_bus *bus; if (!bus_ident && !port_ident) return memdev; cxl_bus_foreach(ctx, bus) { struct cxl_port *port, *top; port = cxl_bus_get_port(bus); if (util_cxl_bus_filter(bus, bus_ident)) if (__memdev_filter_by_port(memdev, port, cxl_bus_get_devname(bus))) return memdev; if (__memdev_filter_by_port(memdev, port, port_ident)) return memdev; top = port; cxl_port_foreach_all(top, port) if (__memdev_filter_by_port(memdev, port, port_ident)) return memdev; } return NULL; } static struct cxl_region * util_cxl_region_filter_by_bus(struct cxl_region *region, const char *__ident) { struct cxl_decoder *decoder = cxl_region_get_decoder(region); if (!util_cxl_decoder_filter_by_bus(decoder, __ident)) return NULL; return region; } static struct cxl_region * util_cxl_region_filter_by_port(struct cxl_region *region, const char *__ident) { struct cxl_decoder *decoder = cxl_region_get_decoder(region); struct cxl_port *port = cxl_decoder_get_port(decoder); if (!util_cxl_port_filter(port, __ident ,CXL_PF_ANCESTRY)) return NULL; return region; } static struct cxl_region * util_cxl_region_filter_by_decoder(struct cxl_region *region, const char *__ident) { struct cxl_decoder *decoder = cxl_region_get_decoder(region); if (!util_cxl_decoder_filter(decoder, __ident)) return NULL; return region; } struct cxl_region *util_cxl_region_filter(struct cxl_region *region, const char *__ident) { char *ident, *save; const char *name; int id; if (!__ident) return region; ident = strdup(__ident); if (!ident) return NULL; for (name = strtok_r(ident, which_sep(__ident), &save); name; name = strtok_r(NULL, which_sep(__ident), &save)) { if (strcmp(name, "all") == 0) break; if ((sscanf(name, "%d", &id) == 1 || sscanf(name, "region%d", &id) == 1) && cxl_region_get_id(region) == id) break; if (strcmp(name, cxl_region_get_devname(region)) == 0) break; } free(ident); if (name) return region; return NULL; } static struct cxl_decoder * util_cxl_decoder_filter_by_region(struct cxl_decoder *decoder, const char *__ident) { struct cxl_region *region; if (!__ident) return decoder; /* root decoders filter by children */ cxl_region_foreach(decoder, region) if (util_cxl_region_filter(region, __ident)) return decoder; /* switch and endpoint decoders have a 1:1 association with a region */ region = cxl_decoder_get_region(decoder); if (!region) return NULL; region = util_cxl_region_filter(region, __ident); if (!region) return NULL; return decoder; } static void splice_array(struct cxl_filter_params *p, struct json_object *jobjs, struct json_object *platform, const char *container_name, bool do_container) { size_t count; if (!json_object_array_length(jobjs)) { json_object_put(jobjs); return; } if (do_container) { struct json_object *container = json_object_new_object(); if (!container) { err(p, "failed to list: %s\n", container_name); return; } json_object_object_add(container, container_name, jobjs); json_object_array_add(platform, container); return; } for (count = json_object_array_length(jobjs); count; count--) { struct json_object *jobj = json_object_array_get_idx(jobjs, 0); json_object_get(jobj); json_object_array_del_idx(jobjs, 0, 1); json_object_array_add(platform, jobj); } json_object_put(jobjs); } static bool cond_add_put_array(struct json_object *jobj, const char *key, struct json_object *array) { if (jobj && array && json_object_array_length(array) > 0) { json_object_object_add(jobj, key, array); return true; } else { json_object_put(array); return false; } } static bool cond_add_put_array_suffix(struct json_object *jobj, const char *key, const char *suffix, struct json_object *array) { char *name; bool rc; if (asprintf(&name, "%s:%s", key, suffix) < 0) return false; rc = cond_add_put_array(jobj, name, array); free(name); return rc; } static struct json_object *pick_array(struct json_object *child, struct json_object *container) { if (child) return child; if (container) return container; return NULL; } static void walk_regions(struct cxl_decoder *decoder, struct json_object *jregions, struct cxl_filter_params *p, unsigned long flags) { struct json_object *jregion; struct cxl_region *region; cxl_region_foreach(decoder, region) { if (!util_cxl_region_filter(region, p->region_filter)) continue; if (!util_cxl_region_filter_by_bus(region, p->bus_filter)) continue; if (!util_cxl_region_filter_by_port(region, p->port_filter)) continue; if (!util_cxl_region_filter_by_decoder(region, p->decoder_filter)) continue; if (!p->idle && !cxl_region_is_enabled(region)) continue; jregion = util_cxl_region_to_json(region, flags); if (!jregion) continue; json_object_array_add(jregions, jregion); } return; } static void walk_decoders(struct cxl_port *port, struct cxl_filter_params *p, struct json_object *jdecoders, struct json_object *jregions, unsigned long flags) { struct cxl_decoder *decoder; cxl_decoder_foreach(port, decoder) { const char *devname = cxl_decoder_get_devname(decoder); struct json_object *jchildregions = NULL; struct json_object *jdecoder = NULL; if (!p->decoders) goto walk_children; if (!util_cxl_decoder_filter(decoder, p->decoder_filter)) goto walk_children; if (!util_cxl_decoder_filter_by_bus(decoder, p->bus_filter)) goto walk_children; if (!util_cxl_decoder_filter_by_port(decoder, p->port_filter, pf_mode(p))) goto walk_children; if (!util_cxl_decoder_filter_by_memdev( decoder, p->memdev_filter, p->serial_filter)) goto walk_children; if (!util_cxl_decoder_filter_by_region(decoder, p->region_filter)) goto walk_children; if (!p->idle && cxl_decoder_get_size(decoder) == 0) continue; jdecoder = util_cxl_decoder_to_json(decoder, flags); if (!decoder) { dbg(p, "decoder object allocation failure\n"); continue; } util_cxl_targets_append_json(jdecoder, decoder, p->memdev_filter, p->serial_filter, flags); if (p->regions) { jchildregions = json_object_new_array(); if (!jchildregions) { err(p, "failed to allocate region object\n"); return; } } json_object_array_add(jdecoders, jdecoder); walk_children: if (!p->regions) continue; if (!cxl_port_is_root(port)) continue; walk_regions(decoder, pick_array(jchildregions, jregions), p, flags); cond_add_put_array_suffix(jdecoder, "regions", devname, jchildregions); } } static void walk_endpoints(struct cxl_port *port, struct cxl_filter_params *p, struct json_object *jeps, struct json_object *jdevs, struct json_object *jdecoders, unsigned long flags) { struct cxl_endpoint *endpoint; cxl_endpoint_foreach(port, endpoint) { struct cxl_port *ep_port = cxl_endpoint_get_port(endpoint); const char *devname = cxl_endpoint_get_devname(endpoint); struct json_object *jchilddecoders = NULL; struct json_object *jendpoint = NULL; struct cxl_memdev *memdev; if (!util_cxl_endpoint_filter(endpoint, p->endpoint_filter)) continue; if (!util_cxl_port_filter_by_bus(ep_port, p->bus_filter)) continue; if (!util_cxl_endpoint_filter_by_port(endpoint, p->port_filter, pf_mode(p))) continue; if (!util_cxl_endpoint_filter_by_memdev( endpoint, p->memdev_filter, p->serial_filter)) continue; if (!p->idle && !cxl_endpoint_is_enabled(endpoint)) continue; if (p->endpoints) { jendpoint = util_cxl_endpoint_to_json(endpoint, flags); if (!jendpoint) { err(p, "%s: failed to list\n", devname); continue; } json_object_array_add(jeps, jendpoint); } if (p->memdevs) { struct json_object *jobj; memdev = cxl_endpoint_get_memdev(endpoint); if (!memdev) continue; if (!util_cxl_memdev_filter(memdev, p->memdev_filter, p->serial_filter)) continue; if (!util_cxl_memdev_filter_by_decoder( memdev, p->decoder_filter)) continue; if (!util_cxl_memdev_filter_by_port( memdev, p->bus_filter, p->port_filter)) continue; if (!p->idle && !cxl_memdev_is_enabled(memdev)) continue; jobj = util_cxl_memdev_to_json(memdev, flags); if (!jobj) { err(p, "failed to json serialize %s\n", cxl_memdev_get_devname(memdev)); continue; } if (p->endpoints) json_object_object_add(jendpoint, "memdev", jobj); else json_object_array_add(jdevs, jobj); } if (p->decoders && p->endpoints) { jchilddecoders = json_object_new_array(); if (!jchilddecoders) { err(p, "%s: failed to enumerate child decoders\n", devname); continue; } } if (!p->decoders) continue; walk_decoders(cxl_endpoint_get_port(endpoint), p, pick_array(jchilddecoders, jdecoders), NULL, flags); cond_add_put_array_suffix(jendpoint, "decoders", devname, jchilddecoders); } } static void walk_child_ports(struct cxl_port *parent_port, struct cxl_filter_params *p, struct json_object *jports, struct json_object *jportdecoders, struct json_object *jeps, struct json_object *jepdecoders, struct json_object *jdevs, unsigned long flags) { struct cxl_port *port; cxl_port_foreach(parent_port, port) { const char *devname = cxl_port_get_devname(port); struct json_object *jport = NULL; struct json_object *jchilddevs = NULL; struct json_object *jchildports = NULL; struct json_object *jchildeps = NULL; struct json_object *jchilddecoders = NULL; if (!util_cxl_port_filter_by_memdev(port, p->memdev_filter, p->serial_filter)) continue; if (!util_cxl_port_filter(port, p->port_filter, pf_mode(p))) goto walk_children; if (!util_cxl_port_filter_by_bus(port, p->bus_filter)) goto walk_children; if (!p->idle && !cxl_port_is_enabled(port)) continue; if (p->ports) { jport = util_cxl_port_to_json(port, flags); if (!jport) { err(p, "%s: failed to list\n", devname); continue; } util_cxl_dports_append_json(jport, port, p->memdev_filter, p->serial_filter, flags); json_object_array_add(jports, jport); jchildports = json_object_new_array(); if (!jchildports) { err(p, "%s: failed to enumerate child ports\n", devname); continue; } if (p->memdevs) { jchilddevs = json_object_new_array(); if (!jchilddevs) { err(p, "%s: failed to enumerate child memdevs\n", devname); continue; } } if (p->endpoints) { jchildeps = json_object_new_array(); if (!jchildeps) { err(p, "%s: failed to enumerate child endpoints\n", devname); continue; } } if (p->decoders) { jchilddecoders = json_object_new_array(); if (!jchilddecoders) { err(p, "%s: failed to enumerate child decoders\n", devname); continue; } } } walk_children: if (p->endpoints || p->memdevs || p->decoders) walk_endpoints(port, p, pick_array(jchildeps, jeps), pick_array(jchilddevs, jdevs), pick_array(jchilddecoders, jepdecoders), flags); walk_decoders(port, p, pick_array(jchilddecoders, jportdecoders), NULL, flags); walk_child_ports(port, p, pick_array(jchildports, jports), pick_array(jchilddecoders, jportdecoders), pick_array(jchildeps, jeps), pick_array(jchilddecoders, jepdecoders), pick_array(jchilddevs, jdevs), flags); cond_add_put_array_suffix(jport, "ports", devname, jchildports); cond_add_put_array_suffix(jport, "endpoints", devname, jchildeps); cond_add_put_array_suffix(jport, "decoders", devname, jchilddecoders); cond_add_put_array_suffix(jport, "memdevs", devname, jchilddevs); } } struct json_object *cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *p) { struct json_object *jdevs = NULL, *jbuses = NULL, *jports = NULL; struct json_object *jplatform = json_object_new_array(); unsigned long flags = cxl_filter_to_flags(p); struct json_object *jportdecoders = NULL; struct json_object *jbusdecoders = NULL; struct json_object *jepdecoders = NULL; struct json_object *janondevs = NULL; struct json_object *jregions = NULL; struct json_object *jeps = NULL; struct cxl_memdev *memdev; int top_level_objs = 0; struct cxl_bus *bus; if (!jplatform) { dbg(p, "platform object allocation failure\n"); return NULL; } janondevs = json_object_new_array(); if (!janondevs) goto err; jbuses = json_object_new_array(); if (!jbuses) goto err; jports = json_object_new_array(); if (!jports) goto err; jeps = json_object_new_array(); if (!jeps) goto err; jdevs = json_object_new_array(); if (!jdevs) goto err; jbusdecoders = json_object_new_array(); if (!jbusdecoders) goto err; jportdecoders = json_object_new_array(); if (!jportdecoders) goto err; jepdecoders = json_object_new_array(); if (!jepdecoders) goto err; jregions = json_object_new_array(); if (!jregions) goto err; dbg(p, "walk memdevs\n"); cxl_memdev_foreach(ctx, memdev) { struct json_object *janondev; if (!util_cxl_memdev_filter(memdev, p->memdev_filter, p->serial_filter)) continue; if (cxl_memdev_is_enabled(memdev)) continue; if (!p->idle) continue; if (p->memdevs) { janondev = util_cxl_memdev_to_json(memdev, flags); if (!janondev) { dbg(p, "memdev object allocation failure\n"); continue; } json_object_array_add(janondevs, janondev); } } dbg(p, "walk buses\n"); cxl_bus_foreach(ctx, bus) { struct json_object *jbus = NULL; struct json_object *jchilddecoders = NULL; struct json_object *jchildports = NULL; struct json_object *jchilddevs = NULL; struct json_object *jchildeps = NULL; struct json_object *jchildregions = NULL; struct cxl_port *port = cxl_bus_get_port(bus); const char *devname = cxl_bus_get_devname(bus); if (!util_cxl_bus_filter_by_memdev(bus, p->memdev_filter, p->serial_filter)) continue; if (!util_cxl_bus_filter(bus, p->bus_filter)) goto walk_children; if (!util_cxl_port_filter(port, p->port_filter, pf_mode(p))) goto walk_children; if (p->buses) { jbus = util_cxl_bus_to_json(bus, flags); if (!jbus) { dbg(p, "bus object allocation failure\n"); continue; } util_cxl_dports_append_json(jbus, port, p->memdev_filter, p->serial_filter, flags); json_object_array_add(jbuses, jbus); if (p->ports) { jchildports = json_object_new_array(); if (!jchildports) { err(p, "%s: failed to enumerate child ports\n", devname); continue; } } if (p->endpoints) { jchildeps = json_object_new_array(); if (!jchildeps) { err(p, "%s: failed to enumerate child endpoints\n", devname); continue; } } if (p->memdevs) { jchilddevs = json_object_new_array(); if (!jchilddevs) { err(p, "%s: failed to enumerate child memdevs\n", devname); continue; } } if (p->decoders) { jchilddecoders = json_object_new_array(); if (!jchilddecoders) { err(p, "%s: failed to enumerate child decoders\n", devname); continue; } } if (p->regions) { jchildregions = json_object_new_array(); if (!jchildregions) { err(p, "%s: failed to enumerate child regions\n", devname); continue; } } } walk_children: dbg(p, "walk decoders\n"); walk_decoders(port, p, pick_array(jchilddecoders, jbusdecoders), pick_array(jchildregions, jregions), flags); dbg(p, "walk rch endpoints\n"); if (p->endpoints || p->memdevs || p->decoders) walk_endpoints(port, p, pick_array(jchildeps, jeps), pick_array(jchilddevs, jdevs), pick_array(jchilddecoders, jepdecoders), flags); dbg(p, "walk ports\n"); walk_child_ports(port, p, pick_array(jchildports, jports), pick_array(jchilddecoders, jportdecoders), pick_array(jchildeps, jeps), pick_array(jchilddecoders, jepdecoders), pick_array(jchilddevs, jdevs), flags); cond_add_put_array_suffix(jbus, "ports", devname, jchildports); cond_add_put_array_suffix(jbus, "endpoints", devname, jchildeps); cond_add_put_array_suffix(jbus, "decoders", devname, jchilddecoders); cond_add_put_array_suffix(jbus, "regions", devname, jchildregions); cond_add_put_array_suffix(jbus, "memdevs", devname, jchilddevs); } if (json_object_array_length(janondevs)) top_level_objs++; if (json_object_array_length(jbuses)) top_level_objs++; if (json_object_array_length(jports)) top_level_objs++; if (json_object_array_length(jeps)) top_level_objs++; if (json_object_array_length(jdevs)) top_level_objs++; if (json_object_array_length(jbusdecoders)) top_level_objs++; if (json_object_array_length(jportdecoders)) top_level_objs++; if (json_object_array_length(jepdecoders)) top_level_objs++; if (json_object_array_length(jregions)) top_level_objs++; splice_array(p, janondevs, jplatform, "anon memdevs", top_level_objs > 1); splice_array(p, jbuses, jplatform, "buses", top_level_objs > 1); splice_array(p, jports, jplatform, "ports", top_level_objs > 1); splice_array(p, jeps, jplatform, "endpoints", top_level_objs > 1); splice_array(p, jdevs, jplatform, "memdevs", top_level_objs > 1); splice_array(p, jbusdecoders, jplatform, "root decoders", top_level_objs > 1); splice_array(p, jportdecoders, jplatform, "port decoders", top_level_objs > 1); splice_array(p, jepdecoders, jplatform, "endpoint decoders", top_level_objs > 1); splice_array(p, jregions, jplatform, "regions", top_level_objs > 1); return jplatform; err: json_object_put(janondevs); json_object_put(jbuses); json_object_put(jports); json_object_put(jeps); json_object_put(jdevs); json_object_put(jbusdecoders); json_object_put(jportdecoders); json_object_put(jepdecoders); json_object_put(jregions); json_object_put(jplatform); return NULL; } ndctl-81/cxl/filter.h000066400000000000000000000054241476737544500146670ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (C) 2021 Intel Corporation. All rights reserved. */ #ifndef _CXL_UTIL_FILTER_H_ #define _CXL_UTIL_FILTER_H_ #include #include #include struct cxl_filter_params { const char *memdev_filter; const char *serial_filter; const char *bus_filter; const char *port_filter; const char *endpoint_filter; const char *decoder_filter; const char *region_filter; bool single; bool endpoints; bool decoders; bool regions; bool targets; bool memdevs; bool ports; bool buses; bool idle; bool human; bool health; bool partition; bool fw; bool alert_config; bool dax; bool media_errors; int verbose; struct log_ctx ctx; }; struct cxl_memdev *util_cxl_memdev_filter(struct cxl_memdev *memdev, const char *__ident, const char *serials); struct cxl_memdev *util_cxl_memdev_filter_by_bus(struct cxl_memdev *memdev, const char *__ident); struct cxl_port *util_cxl_port_filter_by_memdev(struct cxl_port *port, const char *ident, const char *serial); struct cxl_decoder *util_cxl_decoder_filter(struct cxl_decoder *decoder, const char *__ident); struct cxl_region *util_cxl_region_filter(struct cxl_region *region, const char *__ident); enum cxl_port_filter_mode { CXL_PF_SINGLE, CXL_PF_ANCESTRY, }; struct cxl_port *util_cxl_port_filter(struct cxl_port *port, const char *ident, enum cxl_port_filter_mode mode); struct cxl_bus *util_cxl_bus_filter(struct cxl_bus *bus, const char *__ident); struct cxl_endpoint *util_cxl_endpoint_filter(struct cxl_endpoint *endpoint, const char *__ident); struct cxl_target *util_cxl_target_filter_by_memdev(struct cxl_target *target, const char *ident, const char *serial); struct cxl_dport *util_cxl_dport_filter_by_memdev(struct cxl_dport *dport, const char *ident, const char *serial); struct cxl_decoder *util_cxl_decoder_filter(struct cxl_decoder *decoder, const char *__ident); struct json_object *cxl_filter_walk(struct cxl_ctx *ctx, struct cxl_filter_params *param); static inline unsigned long cxl_filter_to_flags(struct cxl_filter_params *param) { unsigned long flags = 0; if (param->idle) flags |= UTIL_JSON_IDLE; if (param->human) flags |= UTIL_JSON_HUMAN; if (param->health) flags |= UTIL_JSON_HEALTH; if (param->targets) flags |= UTIL_JSON_TARGETS; if (param->partition) flags |= UTIL_JSON_PARTITION; if (param->fw) flags |= UTIL_JSON_FIRMWARE; if (param->alert_config) flags |= UTIL_JSON_ALERT_CONFIG; if (param->dax) flags |= UTIL_JSON_DAX | UTIL_JSON_DAX_DEVS; if (param->media_errors) flags |= UTIL_JSON_MEDIA_ERRORS; return flags; } bool cxl_filter_has(const char *needle, const char *__filter); #endif /* _CXL_UTIL_FILTER_H_ */ ndctl-81/cxl/json.c000066400000000000000000001070761476737544500143540ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2015-2021 Intel Corporation. All rights reserved. #include #include #include #include #include #include #include #include #include #include #include "filter.h" #include "json.h" #include "../daxctl/json.h" #include "../util/event_trace.h" #define CXL_FW_VERSION_STR_LEN 16 #define CXL_FW_MAX_SLOTS 4 static struct json_object *util_cxl_memdev_fw_to_json( struct cxl_memdev *memdev, unsigned long flags) { struct json_object *jobj; struct json_object *jfw; u32 field, num_slots; struct cxl_cmd *cmd; size_t remaining; int rc, i; jfw = json_object_new_object(); if (!jfw) return NULL; if (!memdev) goto err_jobj; cmd = cxl_cmd_new_get_fw_info(memdev); if (!cmd) goto err_jobj; rc = cxl_cmd_submit(cmd); if (rc < 0) goto err_cmd; rc = cxl_cmd_get_mbox_status(cmd); if (rc != 0) goto err_cmd; /* fw_info fields */ num_slots = cxl_cmd_fw_info_get_num_slots(cmd); jobj = json_object_new_int(num_slots); if (jobj) json_object_object_add(jfw, "num_slots", jobj); field = cxl_cmd_fw_info_get_active_slot(cmd); jobj = json_object_new_int(field); if (jobj) json_object_object_add(jfw, "active_slot", jobj); field = cxl_cmd_fw_info_get_staged_slot(cmd); if (field > 0 && field <= num_slots) { jobj = json_object_new_int(field); if (jobj) json_object_object_add(jfw, "staged_slot", jobj); } rc = cxl_cmd_fw_info_get_online_activate_capable(cmd); jobj = json_object_new_boolean(rc); if (jobj) json_object_object_add(jfw, "online_activate_capable", jobj); for (i = 1; i <= CXL_FW_MAX_SLOTS; i++) { char fw_ver[CXL_FW_VERSION_STR_LEN + 1]; char jkey[16]; rc = cxl_cmd_fw_info_get_fw_ver(cmd, i, fw_ver, CXL_FW_VERSION_STR_LEN); if (rc) continue; fw_ver[CXL_FW_VERSION_STR_LEN] = 0; snprintf(jkey, 16, "slot_%d_version", i); jobj = json_object_new_string(fw_ver); if (jobj) json_object_object_add(jfw, jkey, jobj); } rc = cxl_memdev_fw_update_in_progress(memdev); jobj = json_object_new_boolean(rc); if (jobj) json_object_object_add(jfw, "fw_update_in_progress", jobj); if (rc == true) { remaining = cxl_memdev_fw_update_get_remaining(memdev); jobj = util_json_object_size(remaining, flags); if (jobj) json_object_object_add(jfw, "remaining_size", jobj); } cxl_cmd_unref(cmd); return jfw; err_cmd: cxl_cmd_unref(cmd); err_jobj: json_object_put(jfw); return NULL; } static struct json_object *util_cxl_memdev_health_to_json( struct cxl_memdev *memdev, unsigned long flags) { struct json_object *jhealth; struct json_object *jobj; struct cxl_cmd *cmd; u32 field; int rc; jhealth = json_object_new_object(); if (!jhealth) return NULL; if (!memdev) goto err_jobj; cmd = cxl_cmd_new_get_health_info(memdev); if (!cmd) goto err_jobj; rc = cxl_cmd_submit(cmd); if (rc < 0) goto err_cmd; rc = cxl_cmd_get_mbox_status(cmd); if (rc != 0) goto err_cmd; /* health_status fields */ rc = cxl_cmd_health_info_get_maintenance_needed(cmd); jobj = json_object_new_boolean(rc); if (jobj) json_object_object_add(jhealth, "maintenance_needed", jobj); rc = cxl_cmd_health_info_get_performance_degraded(cmd); jobj = json_object_new_boolean(rc); if (jobj) json_object_object_add(jhealth, "performance_degraded", jobj); rc = cxl_cmd_health_info_get_hw_replacement_needed(cmd); jobj = json_object_new_boolean(rc); if (jobj) json_object_object_add(jhealth, "hw_replacement_needed", jobj); /* media_status fields */ rc = cxl_cmd_health_info_get_media_normal(cmd); jobj = json_object_new_boolean(rc); if (jobj) json_object_object_add(jhealth, "media_normal", jobj); rc = cxl_cmd_health_info_get_media_not_ready(cmd); jobj = json_object_new_boolean(rc); if (jobj) json_object_object_add(jhealth, "media_not_ready", jobj); rc = cxl_cmd_health_info_get_media_persistence_lost(cmd); jobj = json_object_new_boolean(rc); if (jobj) json_object_object_add(jhealth, "media_persistence_lost", jobj); rc = cxl_cmd_health_info_get_media_data_lost(cmd); jobj = json_object_new_boolean(rc); if (jobj) json_object_object_add(jhealth, "media_data_lost", jobj); rc = cxl_cmd_health_info_get_media_powerloss_persistence_loss(cmd); jobj = json_object_new_boolean(rc); if (jobj) json_object_object_add(jhealth, "media_powerloss_persistence_loss", jobj); rc = cxl_cmd_health_info_get_media_shutdown_persistence_loss(cmd); jobj = json_object_new_boolean(rc); if (jobj) json_object_object_add(jhealth, "media_shutdown_persistence_loss", jobj); rc = cxl_cmd_health_info_get_media_persistence_loss_imminent(cmd); jobj = json_object_new_boolean(rc); if (jobj) json_object_object_add(jhealth, "media_persistence_loss_imminent", jobj); rc = cxl_cmd_health_info_get_media_powerloss_data_loss(cmd); jobj = json_object_new_boolean(rc); if (jobj) json_object_object_add(jhealth, "media_powerloss_data_loss", jobj); rc = cxl_cmd_health_info_get_media_shutdown_data_loss(cmd); jobj = json_object_new_boolean(rc); if (jobj) json_object_object_add(jhealth, "media_shutdown_data_loss", jobj); rc = cxl_cmd_health_info_get_media_data_loss_imminent(cmd); jobj = json_object_new_boolean(rc); if (jobj) json_object_object_add(jhealth, "media_data_loss_imminent", jobj); /* ext_status fields */ if (cxl_cmd_health_info_get_ext_life_used_normal(cmd)) jobj = json_object_new_string("normal"); else if (cxl_cmd_health_info_get_ext_life_used_warning(cmd)) jobj = json_object_new_string("warning"); else if (cxl_cmd_health_info_get_ext_life_used_critical(cmd)) jobj = json_object_new_string("critical"); else jobj = json_object_new_string("unknown"); if (jobj) json_object_object_add(jhealth, "ext_life_used", jobj); if (cxl_cmd_health_info_get_ext_temperature_normal(cmd)) jobj = json_object_new_string("normal"); else if (cxl_cmd_health_info_get_ext_temperature_warning(cmd)) jobj = json_object_new_string("warning"); else if (cxl_cmd_health_info_get_ext_temperature_critical(cmd)) jobj = json_object_new_string("critical"); else jobj = json_object_new_string("unknown"); if (jobj) json_object_object_add(jhealth, "ext_temperature", jobj); if (cxl_cmd_health_info_get_ext_corrected_volatile_normal(cmd)) jobj = json_object_new_string("normal"); else if (cxl_cmd_health_info_get_ext_corrected_volatile_warning(cmd)) jobj = json_object_new_string("warning"); else jobj = json_object_new_string("unknown"); if (jobj) json_object_object_add(jhealth, "ext_corrected_volatile", jobj); if (cxl_cmd_health_info_get_ext_corrected_persistent_normal(cmd)) jobj = json_object_new_string("normal"); else if (cxl_cmd_health_info_get_ext_corrected_persistent_warning(cmd)) jobj = json_object_new_string("warning"); else jobj = json_object_new_string("unknown"); if (jobj) json_object_object_add(jhealth, "ext_corrected_persistent", jobj); /* other fields */ field = cxl_cmd_health_info_get_life_used(cmd); if (field != 0xff) { jobj = json_object_new_int(field); if (jobj) json_object_object_add(jhealth, "life_used_percent", jobj); } field = cxl_cmd_health_info_get_temperature(cmd); if (field != 0xffff) { jobj = json_object_new_int(field); if (jobj) json_object_object_add(jhealth, "temperature", jobj); } field = cxl_cmd_health_info_get_dirty_shutdowns(cmd); jobj = util_json_new_u64(field); if (jobj) json_object_object_add(jhealth, "dirty_shutdowns", jobj); field = cxl_cmd_health_info_get_volatile_errors(cmd); jobj = util_json_new_u64(field); if (jobj) json_object_object_add(jhealth, "volatile_errors", jobj); field = cxl_cmd_health_info_get_pmem_errors(cmd); jobj = util_json_new_u64(field); if (jobj) json_object_object_add(jhealth, "pmem_errors", jobj); cxl_cmd_unref(cmd); return jhealth; err_cmd: cxl_cmd_unref(cmd); err_jobj: json_object_put(jhealth); return NULL; } static struct json_object * util_cxl_memdev_alert_config_to_json(struct cxl_memdev *memdev, unsigned long flags) { struct json_object *jalert_config; struct json_object *jobj; struct cxl_cmd *cmd; int rc; jalert_config = json_object_new_object(); if (!jalert_config) return NULL; if (!memdev) goto err_jobj; cmd = cxl_cmd_new_get_alert_config(memdev); if (!cmd) goto err_jobj; rc = cxl_cmd_submit(cmd); if (rc < 0) goto err_cmd; rc = cxl_cmd_get_mbox_status(cmd); if (rc != 0) goto err_cmd; rc = cxl_cmd_alert_config_life_used_prog_warn_threshold_valid(cmd); jobj = json_object_new_boolean(rc); if (jobj) json_object_object_add(jalert_config, "life_used_prog_warn_threshold_valid", jobj); rc = cxl_cmd_alert_config_dev_over_temperature_prog_warn_threshold_valid( cmd); jobj = json_object_new_boolean(rc); if (jobj) json_object_object_add( jalert_config, "dev_over_temperature_prog_warn_threshold_valid", jobj); rc = cxl_cmd_alert_config_dev_under_temperature_prog_warn_threshold_valid( cmd); jobj = json_object_new_boolean(rc); if (jobj) json_object_object_add( jalert_config, "dev_under_temperature_prog_warn_threshold_valid", jobj); rc = cxl_cmd_alert_config_corrected_volatile_mem_err_prog_warn_threshold_valid( cmd); jobj = json_object_new_boolean(rc); if (jobj) json_object_object_add( jalert_config, "corrected_volatile_mem_err_prog_warn_threshold_valid", jobj); rc = cxl_cmd_alert_config_corrected_pmem_err_prog_warn_threshold_valid( cmd); jobj = json_object_new_boolean(rc); if (jobj) json_object_object_add( jalert_config, "corrected_pmem_err_prog_warn_threshold_valid", jobj); rc = cxl_cmd_alert_config_life_used_prog_warn_threshold_writable(cmd); jobj = json_object_new_boolean(rc); if (jobj) json_object_object_add(jalert_config, "life_used_prog_warn_threshold_writable", jobj); rc = cxl_cmd_alert_config_dev_over_temperature_prog_warn_threshold_writable( cmd); jobj = json_object_new_boolean(rc); if (jobj) json_object_object_add( jalert_config, "dev_over_temperature_prog_warn_threshold_writable", jobj); rc = cxl_cmd_alert_config_dev_under_temperature_prog_warn_threshold_writable( cmd); jobj = json_object_new_boolean(rc); if (jobj) json_object_object_add( jalert_config, "dev_under_temperature_prog_warn_threshold_writable", jobj); rc = cxl_cmd_alert_config_corrected_volatile_mem_err_prog_warn_threshold_writable( cmd); jobj = json_object_new_boolean(rc); if (jobj) json_object_object_add( jalert_config, "corrected_volatile_mem_err_prog_warn_threshold_writable", jobj); rc = cxl_cmd_alert_config_corrected_pmem_err_prog_warn_threshold_writable( cmd); jobj = json_object_new_boolean(rc); if (jobj) json_object_object_add( jalert_config, "corrected_pmem_err_prog_warn_threshold_writable", jobj); rc = cxl_cmd_alert_config_get_life_used_crit_alert_threshold(cmd); jobj = json_object_new_int(rc); if (jobj) json_object_object_add(jalert_config, "life_used_crit_alert_threshold", jobj); rc = cxl_cmd_alert_config_get_life_used_prog_warn_threshold(cmd); jobj = json_object_new_int(rc); if (jobj) json_object_object_add(jalert_config, "life_used_prog_warn_threshold", jobj); rc = cxl_cmd_alert_config_get_dev_over_temperature_crit_alert_threshold( cmd); jobj = json_object_new_int(rc); if (jobj) json_object_object_add( jalert_config, "dev_over_temperature_crit_alert_threshold", jobj); rc = cxl_cmd_alert_config_get_dev_under_temperature_crit_alert_threshold( cmd); jobj = json_object_new_int(rc); if (jobj) json_object_object_add( jalert_config, "dev_under_temperature_crit_alert_threshold", jobj); rc = cxl_cmd_alert_config_get_dev_over_temperature_prog_warn_threshold( cmd); jobj = json_object_new_int(rc); if (jobj) json_object_object_add( jalert_config, "dev_over_temperature_prog_warn_threshold", jobj); rc = cxl_cmd_alert_config_get_dev_under_temperature_prog_warn_threshold( cmd); jobj = json_object_new_int(rc); if (jobj) json_object_object_add( jalert_config, "dev_under_temperature_prog_warn_threshold", jobj); rc = cxl_cmd_alert_config_get_corrected_volatile_mem_err_prog_warn_threshold( cmd); jobj = json_object_new_int(rc); if (jobj) json_object_object_add( jalert_config, "corrected_volatile_mem_err_prog_warn_threshold", jobj); rc = cxl_cmd_alert_config_get_corrected_pmem_err_prog_warn_threshold( cmd); jobj = json_object_new_int(rc); if (jobj) json_object_object_add(jalert_config, "corrected_pmem_err_prog_warn_threshold", jobj); cxl_cmd_unref(cmd); return jalert_config; err_cmd: cxl_cmd_unref(cmd); err_jobj: json_object_put(jalert_config); return NULL; } /* * Present complete view of memdev partition by presenting fields from * both GET_PARTITION_INFO and IDENTIFY mailbox commands. */ static struct json_object *util_cxl_memdev_partition_to_json(struct cxl_memdev *memdev, unsigned long flags) { struct json_object *jobj = NULL; struct json_object *jpart; unsigned long long cap; struct cxl_cmd *cmd; int rc; jpart = json_object_new_object(); if (!jpart) return NULL; if (!memdev) goto err_jobj; /* Retrieve partition info in the IDENTIFY mbox cmd */ cmd = cxl_cmd_new_identify(memdev); if (!cmd) goto err_jobj; rc = cxl_cmd_submit(cmd); if (rc < 0) goto err_identify; rc = cxl_cmd_get_mbox_status(cmd); if (rc != 0) goto err_identify; cap = cxl_cmd_identify_get_total_size(cmd); if (cap != ULLONG_MAX) { jobj = util_json_object_size(cap, flags); if (jobj) json_object_object_add(jpart, "total_size", jobj); } cap = cxl_cmd_identify_get_volatile_only_size(cmd); if (cap != ULLONG_MAX) { jobj = util_json_object_size(cap, flags); if (jobj) json_object_object_add(jpart, "volatile_only_size", jobj); } cap = cxl_cmd_identify_get_persistent_only_size(cmd); if (cap != ULLONG_MAX) { jobj = util_json_object_size(cap, flags); if (jobj) json_object_object_add(jpart, "persistent_only_size", jobj); } cap = cxl_cmd_identify_get_partition_align(cmd); jobj = util_json_object_size(cap, flags); if (jobj) json_object_object_add(jpart, "partition_alignment_size", jobj); cxl_cmd_unref(cmd); /* Return now if there is no partition info to get. */ if (!cap) return jpart; /* Retrieve partition info in GET_PARTITION_INFO mbox cmd */ cmd = cxl_cmd_new_get_partition(memdev); if (!cmd) return jpart; rc = cxl_cmd_submit(cmd); if (rc < 0) goto err_get; rc = cxl_cmd_get_mbox_status(cmd); if (rc != 0) goto err_get; cap = cxl_cmd_partition_get_active_volatile_size(cmd); if (cap != ULLONG_MAX) { jobj = util_json_object_size(cap, flags); if (jobj) json_object_object_add(jpart, "active_volatile_size", jobj); } cap = cxl_cmd_partition_get_active_persistent_size(cmd); if (cap != ULLONG_MAX) { jobj = util_json_object_size(cap, flags); if (jobj) json_object_object_add(jpart, "active_persistent_size", jobj); } cap = cxl_cmd_partition_get_next_volatile_size(cmd); if (cap != ULLONG_MAX) { jobj = util_json_object_size(cap, flags); if (jobj) json_object_object_add(jpart, "next_volatile_size", jobj); } cap = cxl_cmd_partition_get_next_persistent_size(cmd); if (cap != ULLONG_MAX) { jobj = util_json_object_size(cap, flags); if (jobj) json_object_object_add(jpart, "next_persistent_size", jobj); } err_get: cxl_cmd_unref(cmd); return jpart; err_identify: cxl_cmd_unref(cmd); err_jobj: json_object_put(jpart); return NULL; } /* CXL Spec 3.1 Table 8-140 Media Error Record */ #define CXL_POISON_SOURCE_MAX 7 static const char *const poison_source[] = { "Unknown", "External", "Internal", "Injected", "Reserved", "Reserved", "Reserved", "Vendor Specific" }; /* CXL Spec 3.1 Table 8-139 Get Poison List Output Payload */ #define CXL_POISON_FLAG_MORE BIT(0) #define CXL_POISON_FLAG_OVERFLOW BIT(1) #define CXL_POISON_FLAG_SCANNING BIT(2) static int poison_event_to_json(struct tep_event *event, struct tep_record *record, struct event_ctx *e_ctx) { struct cxl_poison_ctx *p_ctx = e_ctx->poison_ctx; struct json_object *jp, *jobj, *jpoison = p_ctx->jpoison; struct cxl_memdev *memdev = p_ctx->memdev; struct cxl_region *region = p_ctx->region; unsigned long flags = e_ctx->json_flags; const char *region_name = NULL; char flag_str[32] = { '\0' }; bool overflow = false; u8 source, pflags; u64 offset, ts; u32 length; char *str; int len; jp = json_object_new_object(); if (!jp) return -ENOMEM; /* Skip records not in this region when listing by region */ if (region) region_name = cxl_region_get_devname(region); if (region_name) str = tep_get_field_raw(NULL, event, "region", record, &len, 0); if ((region_name) && (strcmp(region_name, str) != 0)) { json_object_put(jp); return 0; } /* Include offset,length by region (hpa) or by memdev (dpa) */ if (region) { offset = trace_get_field_u64(event, record, "hpa"); if (offset != ULLONG_MAX) { offset = offset - cxl_region_get_resource(region); jobj = util_json_object_hex(offset, flags); if (jobj) json_object_object_add(jp, "offset", jobj); } } else if (memdev) { offset = trace_get_field_u64(event, record, "dpa"); if (offset != ULLONG_MAX) { jobj = util_json_object_hex(offset, flags); if (jobj) json_object_object_add(jp, "offset", jobj); } } length = trace_get_field_u32(event, record, "dpa_length"); jobj = util_json_object_size(length, flags); if (jobj) json_object_object_add(jp, "length", jobj); /* Always include the poison source */ source = trace_get_field_u8(event, record, "source"); if (source <= CXL_POISON_SOURCE_MAX) jobj = json_object_new_string(poison_source[source]); else jobj = json_object_new_string("Reserved"); if (jobj) json_object_object_add(jp, "source", jobj); /* Include flags and overflow time if present */ pflags = trace_get_field_u8(event, record, "flags"); if (pflags && pflags < UCHAR_MAX) { if (pflags & CXL_POISON_FLAG_MORE) strcat(flag_str, "More,"); if (pflags & CXL_POISON_FLAG_SCANNING) strcat(flag_str, "Scanning,"); if (pflags & CXL_POISON_FLAG_OVERFLOW) { strcat(flag_str, "Overflow,"); overflow = true; } jobj = json_object_new_string(flag_str); if (jobj) json_object_object_add(jp, "flags", jobj); } if (overflow) { ts = trace_get_field_u64(event, record, "overflow_ts"); jobj = util_json_object_hex(ts, flags); if (jobj) json_object_object_add(jp, "overflow_t", jobj); } json_object_array_add(jpoison, jp); return 0; } static struct json_object * util_cxl_poison_events_to_json(struct tracefs_instance *inst, struct cxl_poison_ctx *p_ctx, unsigned long flags) { struct event_ctx ectx = { .event_name = "cxl_poison", .event_pid = getpid(), .system = "cxl", .poison_ctx = p_ctx, .json_flags = flags, .parse_event = poison_event_to_json, }; int rc; p_ctx->jpoison = json_object_new_array(); if (!p_ctx->jpoison) return NULL; rc = trace_event_parse(inst, &ectx); if (rc < 0) { fprintf(stderr, "Failed to parse events: %d\n", rc); goto put_jobj; } if (json_object_array_length(p_ctx->jpoison) == 0) goto put_jobj; return p_ctx->jpoison; put_jobj: json_object_put(p_ctx->jpoison); return NULL; } static struct json_object * util_cxl_poison_list_to_json(struct cxl_region *region, struct cxl_memdev *memdev, unsigned long flags) { struct json_object *jpoison = NULL; struct cxl_poison_ctx p_ctx; struct tracefs_instance *inst; int rc; inst = tracefs_instance_create("cxl list"); if (!inst) { fprintf(stderr, "tracefs_instance_create() failed\n"); return NULL; } rc = trace_event_enable(inst, "cxl", "cxl_poison"); if (rc < 0) { fprintf(stderr, "Failed to enable trace: %d\n", rc); goto err_free; } if (region) rc = cxl_region_trigger_poison_list(region); else rc = cxl_memdev_trigger_poison_list(memdev); if (rc) goto err_free; rc = trace_event_disable(inst); if (rc < 0) { fprintf(stderr, "Failed to disable trace: %d\n", rc); goto err_free; } p_ctx = (struct cxl_poison_ctx){ .region = region, .memdev = memdev, }; jpoison = util_cxl_poison_events_to_json(inst, &p_ctx, flags); err_free: tracefs_instance_free(inst); return jpoison; } struct json_object *util_cxl_memdev_to_json(struct cxl_memdev *memdev, unsigned long flags) { const char *devname = cxl_memdev_get_devname(memdev); struct json_object *jdev, *jobj; unsigned long long serial, size; const char *fw_version; int numa_node; int qos_class; jdev = json_object_new_object(); if (!jdev) return NULL; jobj = json_object_new_string(devname); if (jobj) json_object_object_add(jdev, "memdev", jobj); size = cxl_memdev_get_pmem_size(memdev); if (size) { jobj = util_json_object_size(size, flags); if (jobj) json_object_object_add(jdev, "pmem_size", jobj); qos_class = cxl_memdev_get_pmem_qos_class(memdev); if (qos_class != CXL_QOS_CLASS_NONE) { jobj = json_object_new_int(qos_class); if (jobj) json_object_object_add(jdev, "pmem_qos_class", jobj); } } size = cxl_memdev_get_ram_size(memdev); if (size) { jobj = util_json_object_size(size, flags); if (jobj) json_object_object_add(jdev, "ram_size", jobj); qos_class = cxl_memdev_get_ram_qos_class(memdev); if (qos_class != CXL_QOS_CLASS_NONE) { jobj = json_object_new_int(qos_class); if (jobj) json_object_object_add(jdev, "ram_qos_class", jobj); } } if (flags & UTIL_JSON_HEALTH) { jobj = util_cxl_memdev_health_to_json(memdev, flags); if (jobj) json_object_object_add(jdev, "health", jobj); } if (flags & UTIL_JSON_ALERT_CONFIG) { jobj = util_cxl_memdev_alert_config_to_json(memdev, flags); if (jobj) json_object_object_add(jdev, "alert_config", jobj); } serial = cxl_memdev_get_serial(memdev); if (serial < ULLONG_MAX) { jobj = util_json_object_hex(serial, flags); if (jobj) json_object_object_add(jdev, "serial", jobj); } numa_node = cxl_memdev_get_numa_node(memdev); if (numa_node >= 0) { jobj = json_object_new_int(numa_node); if (jobj) json_object_object_add(jdev, "numa_node", jobj); } jobj = json_object_new_string(cxl_memdev_get_host(memdev)); if (jobj) json_object_object_add(jdev, "host", jobj); fw_version = cxl_memdev_get_firmware_version(memdev); if (fw_version) { jobj = json_object_new_string(fw_version); if (jobj) json_object_object_add(jdev, "firmware_version", jobj); } if (!cxl_memdev_is_enabled(memdev)) { jobj = json_object_new_string("disabled"); if (jobj) json_object_object_add(jdev, "state", jobj); } if (flags & UTIL_JSON_PARTITION) { jobj = util_cxl_memdev_partition_to_json(memdev, flags); if (jobj) json_object_object_add(jdev, "partition_info", jobj); } if (flags & UTIL_JSON_FIRMWARE) { jobj = util_cxl_memdev_fw_to_json(memdev, flags); if (jobj) json_object_object_add(jdev, "firmware", jobj); } if (flags & UTIL_JSON_MEDIA_ERRORS) { jobj = util_cxl_poison_list_to_json(NULL, memdev, flags); if (jobj) json_object_object_add(jdev, "media_errors", jobj); } json_object_set_userdata(jdev, memdev, NULL); return jdev; } void util_cxl_dports_append_json(struct json_object *jport, struct cxl_port *port, const char *ident, const char *serial, unsigned long flags) { struct json_object *jobj, *jdports; struct cxl_dport *dport; int val; val = cxl_port_get_nr_dports(port); if (!val || !(flags & UTIL_JSON_TARGETS)) return; jobj = json_object_new_int(val); if (jobj) json_object_object_add(jport, "nr_dports", jobj); jdports = json_object_new_array(); if (!jdports) return; cxl_dport_foreach(port, dport) { struct json_object *jdport; const char *phys_node, *fw_node; if (!util_cxl_dport_filter_by_memdev(dport, ident, serial)) continue; jdport = json_object_new_object(); if (!jdport) continue; jobj = json_object_new_string(cxl_dport_get_devname(dport)); if (jobj) json_object_object_add(jdport, "dport", jobj); phys_node = cxl_dport_get_physical_node(dport); if (phys_node) { jobj = json_object_new_string(phys_node); if (jobj) json_object_object_add(jdport, "alias", jobj); } fw_node = cxl_dport_get_firmware_node(dport); if (fw_node) { jobj = json_object_new_string(fw_node); if (jobj) json_object_object_add(jdport, "alias", jobj); } val = cxl_dport_get_id(dport); jobj = util_json_object_hex(val, flags); if (jobj) json_object_object_add(jdport, "id", jobj); json_object_array_add(jdports, jdport); json_object_set_userdata(jdport, dport, NULL); } json_object_object_add(jport, "dports", jdports); } struct json_object *util_cxl_bus_to_json(struct cxl_bus *bus, unsigned long flags) { const char *devname = cxl_bus_get_devname(bus); struct json_object *jbus, *jobj; jbus = json_object_new_object(); if (!jbus) return NULL; jobj = json_object_new_string(devname); if (jobj) json_object_object_add(jbus, "bus", jobj); jobj = json_object_new_string(cxl_bus_get_provider(bus)); if (jobj) json_object_object_add(jbus, "provider", jobj); json_object_set_userdata(jbus, bus, NULL); return jbus; } struct json_object *util_cxl_decoder_to_json(struct cxl_decoder *decoder, unsigned long flags) { const char *devname = cxl_decoder_get_devname(decoder); struct cxl_port *port = cxl_decoder_get_port(decoder); struct json_object *jdecoder, *jobj; struct cxl_region *region; u64 val, size; jdecoder = json_object_new_object(); if (!jdecoder) return NULL; jobj = json_object_new_string(devname); if (jobj) json_object_object_add(jdecoder, "decoder", jobj); size = cxl_decoder_get_size(decoder); val = cxl_decoder_get_resource(decoder); if (size && val < ULLONG_MAX) { jobj = util_json_object_hex(val, flags); if (jobj) json_object_object_add(jdecoder, "resource", jobj); } if (size && size < ULLONG_MAX) { jobj = util_json_object_size(size, flags); if (jobj) json_object_object_add(jdecoder, "size", jobj); } val = cxl_decoder_get_interleave_ways(decoder); if (val < UINT_MAX) { jobj = json_object_new_int(val); if (jobj) json_object_object_add(jdecoder, "interleave_ways", jobj); /* granularity is a don't care if not interleaving */ if (val > 1) { val = cxl_decoder_get_interleave_granularity(decoder); if (val < UINT_MAX) { jobj = json_object_new_int(val); if (jobj) json_object_object_add( jdecoder, "interleave_granularity", jobj); } } } region = cxl_decoder_get_region(decoder); if (region) { jobj = json_object_new_string(cxl_region_get_devname(region)); if (jobj) json_object_object_add(jdecoder, "region", jobj); } if (size == 0) { jobj = json_object_new_string("disabled"); if (jobj) json_object_object_add(jdecoder, "state", jobj); } if (cxl_port_is_endpoint(port)) { enum cxl_decoder_mode mode = cxl_decoder_get_mode(decoder); size = cxl_decoder_get_dpa_size(decoder); val = cxl_decoder_get_dpa_resource(decoder); if (size && val < ULLONG_MAX) { jobj = util_json_object_hex(val, flags); if (jobj) json_object_object_add(jdecoder, "dpa_resource", jobj); } if (size && size < ULLONG_MAX) { jobj = util_json_object_size(size, flags); if (jobj) json_object_object_add(jdecoder, "dpa_size", jobj); } if (mode > CXL_DECODER_MODE_NONE) { jobj = json_object_new_string(cxl_decoder_mode_name(mode)); if (jobj) json_object_object_add(jdecoder, "mode", jobj); } } if (cxl_port_is_root(port) && cxl_decoder_is_mem_capable(decoder)) { size = cxl_decoder_get_max_available_extent(decoder); if (size < ULLONG_MAX) { jobj = util_json_object_size(size, flags); if (jobj) json_object_object_add(jdecoder, "max_available_extent", jobj); } if (cxl_decoder_is_pmem_capable(decoder)) { jobj = json_object_new_boolean(true); if (jobj) json_object_object_add(jdecoder, "pmem_capable", jobj); } if (cxl_decoder_is_volatile_capable(decoder)) { jobj = json_object_new_boolean(true); if (jobj) json_object_object_add( jdecoder, "volatile_capable", jobj); } } if (cxl_port_is_root(port) && cxl_decoder_is_accelmem_capable(decoder)) { jobj = json_object_new_boolean(true); if (jobj) json_object_object_add(jdecoder, "accelmem_capable", jobj); } if (cxl_port_is_root(port)) { int qos_class = cxl_root_decoder_get_qos_class(decoder); if (qos_class != CXL_QOS_CLASS_NONE) { jobj = json_object_new_int(qos_class); if (jobj) json_object_object_add(jdecoder, "qos_class", jobj); } } json_object_set_userdata(jdecoder, decoder, NULL); return jdecoder; } void util_cxl_mappings_append_json(struct json_object *jregion, struct cxl_region *region, unsigned long flags) { struct json_object *jobj, *jmappings; struct cxl_memdev_mapping *mapping; unsigned int val, nr_mappings; const char *devname; nr_mappings = cxl_region_get_interleave_ways(region); if (!nr_mappings || (nr_mappings == UINT_MAX)) return; if (!(flags & UTIL_JSON_TARGETS)) return; jmappings = json_object_new_array(); if (!jmappings) return; cxl_mapping_foreach(region, mapping) { struct json_object *jmapping; struct cxl_decoder *decoder; struct cxl_memdev *memdev; jmapping = json_object_new_object(); if (!jmapping) continue; val = cxl_mapping_get_position(mapping); if (val < UINT_MAX) { jobj = json_object_new_int(val); if (jobj) json_object_object_add(jmapping, "position", jobj); } decoder = cxl_mapping_get_decoder(mapping); if (!decoder) continue; memdev = cxl_decoder_get_memdev(decoder); if (memdev) { devname = cxl_memdev_get_devname(memdev); jobj = json_object_new_string(devname); if (jobj) json_object_object_add(jmapping, "memdev", jobj); } devname = cxl_decoder_get_devname(decoder); jobj = json_object_new_string(devname); if (jobj) json_object_object_add(jmapping, "decoder", jobj); json_object_array_add(jmappings, jmapping); json_object_set_userdata(jmapping, mapping, NULL); } json_object_object_add(jregion, "mappings", jmappings); } struct json_object *util_cxl_region_to_json(struct cxl_region *region, unsigned long flags) { enum cxl_decoder_mode mode = cxl_region_get_mode(region); const char *devname = cxl_region_get_devname(region); struct json_object *jregion, *jobj; u64 val; jregion = json_object_new_object(); if (!jregion) return NULL; jobj = json_object_new_string(devname); if (jobj) json_object_object_add(jregion, "region", jobj); val = cxl_region_get_resource(region); if (val < ULLONG_MAX) { jobj = util_json_object_hex(val, flags); if (jobj) json_object_object_add(jregion, "resource", jobj); } val = cxl_region_get_size(region); if (val < ULLONG_MAX) { jobj = util_json_object_size(val, flags); if (jobj) json_object_object_add(jregion, "size", jobj); } if (mode != CXL_DECODER_MODE_NONE) { jobj = json_object_new_string(cxl_decoder_mode_name(mode)); if (jobj) json_object_object_add(jregion, "type", jobj); } val = cxl_region_get_interleave_ways(region); if (val < INT_MAX) { jobj = json_object_new_int(val); if (jobj) json_object_object_add(jregion, "interleave_ways", jobj); } val = cxl_region_get_interleave_granularity(region); if (val < INT_MAX) { jobj = json_object_new_int(val); if (jobj) json_object_object_add(jregion, "interleave_granularity", jobj); } if (cxl_region_decode_is_committed(region)) jobj = json_object_new_string("commit"); else jobj = json_object_new_string("reset"); if (jobj) json_object_object_add(jregion, "decode_state", jobj); if (!cxl_region_is_enabled(region)) { jobj = json_object_new_string("disabled"); if (jobj) json_object_object_add(jregion, "state", jobj); } if (flags & UTIL_JSON_MEDIA_ERRORS) { jobj = util_cxl_poison_list_to_json(region, NULL, flags); if (jobj) json_object_object_add(jregion, "media_errors", jobj); } util_cxl_mappings_append_json(jregion, region, flags); if (flags & UTIL_JSON_DAX) { struct daxctl_region *dax_region; dax_region = cxl_region_get_daxctl_region(region); if (dax_region) { jobj = util_daxctl_region_to_json(dax_region, NULL, flags); if (jobj) json_object_object_add(jregion, "daxregion", jobj); } } if (cxl_region_qos_class_mismatch(region)) { jobj = json_object_new_boolean(true); if (jobj) json_object_object_add(jregion, "qos_class_mismatch", jobj); } json_object_set_userdata(jregion, region, NULL); return jregion; } void util_cxl_targets_append_json(struct json_object *jdecoder, struct cxl_decoder *decoder, const char *ident, const char *serial, unsigned long flags) { struct cxl_port *port = cxl_decoder_get_port(decoder); struct json_object *jobj, *jtargets; struct cxl_target *target; int val; /* Endpoints don't have targets, they *are* targets */ if (cxl_port_is_endpoint(port)) return; val = cxl_decoder_get_nr_targets(decoder); jobj = json_object_new_int(val); if (jobj) json_object_object_add(jdecoder, "nr_targets", jobj); if (!(flags & UTIL_JSON_TARGETS) || !cxl_decoder_get_nr_targets(decoder)) return; jtargets = json_object_new_array(); if (!jtargets) return; cxl_target_foreach(decoder, target) { const char *devname; struct json_object *jtarget; const char *phys_node, *fw_node; if (!util_cxl_target_filter_by_memdev(target, ident, serial)) continue; jtarget = json_object_new_object(); if (!jtarget) continue; devname = cxl_target_get_devname(target); jobj = json_object_new_string(devname); if (jobj) json_object_object_add(jtarget, "target", jobj); phys_node = cxl_target_get_physical_node(target); if (phys_node) { jobj = json_object_new_string(phys_node); if (jobj) json_object_object_add(jtarget, "alias", jobj); } fw_node = cxl_target_get_firmware_node(target); if (fw_node) { jobj = json_object_new_string(fw_node); if (jobj) json_object_object_add(jtarget, "alias", jobj); } val = cxl_target_get_position(target); jobj = json_object_new_int(val); if (jobj) json_object_object_add(jtarget, "position", jobj); val = cxl_target_get_id(target); jobj = util_json_object_hex(val, flags); if (jobj) json_object_object_add(jtarget, "id", jobj); json_object_array_add(jtargets, jtarget); json_object_set_userdata(jtarget, target, NULL); } json_object_object_add(jdecoder, "targets", jtargets); } static struct json_object *__util_cxl_port_to_json(struct cxl_port *port, const char *name_key, unsigned long flags) { const char *devname = cxl_port_get_devname(port); struct json_object *jport, *jobj; jport = json_object_new_object(); if (!jport) return NULL; jobj = json_object_new_string(devname); if (jobj) json_object_object_add(jport, name_key, jobj); jobj = json_object_new_string(cxl_port_get_host(port)); if (jobj) json_object_object_add(jport, "host", jobj); if (cxl_port_get_parent_dport(port)) { struct cxl_dport *dport = cxl_port_get_parent_dport(port); jobj = json_object_new_string(cxl_dport_get_devname(dport)); if (jobj) json_object_object_add(jport, "parent_dport", jobj); } jobj = json_object_new_int(cxl_port_get_depth(port)); if (jobj) json_object_object_add(jport, "depth", jobj); if (!cxl_port_is_enabled(port)) { jobj = json_object_new_string("disabled"); if (jobj) json_object_object_add(jport, "state", jobj); } jobj = json_object_new_int(cxl_port_decoders_committed(port)); if (jobj) json_object_object_add(jport, "decoders_committed", jobj); json_object_set_userdata(jport, port, NULL); return jport; } struct json_object *util_cxl_port_to_json(struct cxl_port *port, unsigned long flags) { return __util_cxl_port_to_json(port, "port", flags); } struct json_object *util_cxl_endpoint_to_json(struct cxl_endpoint *endpoint, unsigned long flags) { return __util_cxl_port_to_json(cxl_endpoint_get_port(endpoint), "endpoint", flags); } ndctl-81/cxl/json.h000066400000000000000000000024231476737544500143470ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (C) 2015-2022 Intel Corporation. All rights reserved. */ #ifndef __CXL_UTIL_JSON_H__ #define __CXL_UTIL_JSON_H__ struct cxl_memdev; struct json_object *util_cxl_memdev_to_json(struct cxl_memdev *memdev, unsigned long flags); struct cxl_bus; struct json_object *util_cxl_bus_to_json(struct cxl_bus *bus, unsigned long flags); struct cxl_port; struct json_object *util_cxl_port_to_json(struct cxl_port *port, unsigned long flags); struct json_object *util_cxl_endpoint_to_json(struct cxl_endpoint *endpoint, unsigned long flags); struct json_object *util_cxl_decoder_to_json(struct cxl_decoder *decoder, unsigned long flags); struct json_object *util_cxl_region_to_json(struct cxl_region *region, unsigned long flags); void util_cxl_mappings_append_json(struct json_object *jregion, struct cxl_region *region, unsigned long flags); void util_cxl_targets_append_json(struct json_object *jdecoder, struct cxl_decoder *decoder, const char *ident, const char *serial, unsigned long flags); void util_cxl_dports_append_json(struct json_object *jport, struct cxl_port *port, const char *ident, const char *serial, unsigned long flags); #endif /* __CXL_UTIL_JSON_H__ */ ndctl-81/cxl/lib/000077500000000000000000000000001476737544500137725ustar00rootroot00000000000000ndctl-81/cxl/lib/libcxl.c000066400000000000000000003342171476737544500154250ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-2.1 // Copyright (C) 2020-2021, Intel Corporation. All rights reserved. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "private.h" /** * struct cxl_ctx - library user context to find "nd" instances * * Instantiate with cxl_new(), which takes an initial reference. Free * the context by dropping the reference count to zero with * cxl_unref(), or take additional references with cxl_ref() * @timeout: default library timeout in milliseconds */ struct cxl_ctx { /* log_ctx must be first member for cxl_set_log_fn compat */ struct log_ctx ctx; int refcount; void *userdata; int memdevs_init; int buses_init; unsigned long timeout; struct udev *udev; struct udev_queue *udev_queue; struct list_head memdevs; struct list_head buses; struct kmod_ctx *kmod_ctx; struct daxctl_ctx *daxctl_ctx; void *private_data; }; static void free_pmem(struct cxl_pmem *pmem) { if (pmem) { free(pmem->dev_buf); free(pmem->dev_path); free(pmem); } } static void free_fwl(struct cxl_fw_loader *fwl) { if (fwl) { free(fwl->loading); free(fwl->data); free(fwl->remaining); free(fwl->cancel); free(fwl->status); free(fwl); } } static void free_memdev(struct cxl_memdev *memdev, struct list_head *head) { if (head) list_del_from(head, &memdev->list); kmod_module_unref(memdev->module); free_pmem(memdev->pmem); free_fwl(memdev->fwl); free(memdev->firmware_version); free(memdev->dev_buf); free(memdev->dev_path); free(memdev->host_path); free(memdev); } static void free_target(struct cxl_target *target, struct list_head *head) { if (head) list_del_from(head, &target->list); free(target->dev_path); free(target->phys_path); free(target->fw_path); free(target); } static void free_region(struct cxl_region *region, struct list_head *head) { struct cxl_memdev_mapping *mapping, *_m; list_for_each_safe(®ion->mappings, mapping, _m, list) { list_del_from(®ion->mappings, &mapping->list); free(mapping); } if (head) list_del_from(head, ®ion->list); kmod_module_unref(region->module); free(region->dev_buf); free(region->dev_path); free(region); } static void free_stale_regions(struct cxl_decoder *decoder) { struct cxl_region *region, *_r; list_for_each_safe(&decoder->stale_regions, region, _r, list) free_region(region, &decoder->stale_regions); } static void free_regions(struct cxl_decoder *decoder) { struct cxl_region *region, *_r; list_for_each_safe(&decoder->regions, region, _r, list) free_region(region, &decoder->regions); } static void free_decoder(struct cxl_decoder *decoder, struct list_head *head) { struct cxl_target *target, *_t; if (head) list_del_from(head, &decoder->list); list_for_each_safe(&decoder->targets, target, _t, list) free_target(target, &decoder->targets); free_regions(decoder); free_stale_regions(decoder); free(decoder->dev_buf); free(decoder->dev_path); free(decoder); } static void free_dport(struct cxl_dport *dport, struct list_head *head) { if (head) list_del_from(head, &dport->list); free(dport->dev_buf); free(dport->dev_path); free(dport->phys_path); free(dport->fw_path); free(dport); } static void free_port(struct cxl_port *port, struct list_head *head); static void free_endpoint(struct cxl_endpoint *endpoint, struct list_head *head); static void __free_port(struct cxl_port *port, struct list_head *head) { struct cxl_endpoint *endpoint, *_e; struct cxl_decoder *decoder, *_d; struct cxl_dport *dport, *_dp; struct cxl_port *child, *_c; if (head) list_del_from(head, &port->list); list_for_each_safe(&port->child_ports, child, _c, list) free_port(child, &port->child_ports); list_for_each_safe(&port->endpoints, endpoint, _e, port.list) free_endpoint(endpoint, &port->endpoints); list_for_each_safe(&port->decoders, decoder, _d, list) free_decoder(decoder, &port->decoders); list_for_each_safe(&port->dports, dport, _dp, list) free_dport(dport , &port->dports); kmod_module_unref(port->module); free(port->dev_buf); free(port->dev_path); free(port->uport); free(port->parent_dport_path); } static void free_port(struct cxl_port *port, struct list_head *head) { __free_port(port, head); free(port); } static void free_endpoint(struct cxl_endpoint *endpoint, struct list_head *head) { __free_port(&endpoint->port, head); free(endpoint); } static void free_bus(struct cxl_bus *bus, struct list_head *head) { __free_port(&bus->port, head); free(bus); } /** * cxl_get_userdata - retrieve stored data pointer from library context * @ctx: cxl library context * * This might be useful to access from callbacks like a custom logging * function. */ CXL_EXPORT void *cxl_get_userdata(struct cxl_ctx *ctx) { if (ctx == NULL) return NULL; return ctx->userdata; } /** * cxl_set_userdata - store custom @userdata in the library context * @ctx: cxl library context * @userdata: data pointer */ CXL_EXPORT void cxl_set_userdata(struct cxl_ctx *ctx, void *userdata) { if (ctx == NULL) return; ctx->userdata = userdata; } CXL_EXPORT void cxl_set_private_data(struct cxl_ctx *ctx, void *data) { ctx->private_data = data; } CXL_EXPORT void *cxl_get_private_data(struct cxl_ctx *ctx) { return ctx->private_data; } /** * cxl_new - instantiate a new library context * @ctx: context to establish * * Returns zero on success and stores an opaque pointer in ctx. The * context is freed by cxl_unref(), i.e. cxl_new() implies an * internal cxl_ref(). */ CXL_EXPORT int cxl_new(struct cxl_ctx **ctx) { struct daxctl_ctx *daxctl_ctx; struct udev_queue *udev_queue; struct kmod_ctx *kmod_ctx; struct udev *udev; struct cxl_ctx *c; int rc = 0; c = calloc(1, sizeof(struct cxl_ctx)); if (!c) return -ENOMEM; rc = daxctl_new(&daxctl_ctx); if (rc) goto err_daxctl; kmod_ctx = kmod_new(NULL, NULL); if (check_kmod(kmod_ctx) != 0) { rc = -ENXIO; goto err_kmod; } udev = udev_new(); if (!udev) { rc = -ENOMEM; goto err_udev; } udev_queue = udev_queue_new(udev); if (!udev_queue) { rc = -ENOMEM; goto err_udev_queue; } c->refcount = 1; log_init(&c->ctx, "libcxl", "CXL_LOG"); info(c, "ctx %p created\n", c); dbg(c, "log_priority=%d\n", c->ctx.log_priority); *ctx = c; list_head_init(&c->memdevs); list_head_init(&c->buses); c->kmod_ctx = kmod_ctx; c->daxctl_ctx = daxctl_ctx; c->udev = udev; c->udev_queue = udev_queue; c->timeout = 5000; return 0; err_udev_queue: udev_queue_unref(udev_queue); err_udev: kmod_unref(kmod_ctx); err_kmod: daxctl_unref(daxctl_ctx); err_daxctl: free(c); return rc; } /** * cxl_ref - take an additional reference on the context * @ctx: context established by cxl_new() */ CXL_EXPORT struct cxl_ctx *cxl_ref(struct cxl_ctx *ctx) { if (ctx == NULL) return NULL; ctx->refcount++; return ctx; } /** * cxl_unref - drop a context reference count * @ctx: context established by cxl_new() * * Drop a reference and if the resulting reference count is 0 destroy * the context. */ CXL_EXPORT void cxl_unref(struct cxl_ctx *ctx) { struct cxl_memdev *memdev, *_d; struct cxl_bus *bus, *_b; if (ctx == NULL) return; ctx->refcount--; if (ctx->refcount > 0) return; list_for_each_safe(&ctx->memdevs, memdev, _d, list) free_memdev(memdev, &ctx->memdevs); list_for_each_safe(&ctx->buses, bus, _b, port.list) free_bus(bus, &ctx->buses); udev_queue_unref(ctx->udev_queue); udev_unref(ctx->udev); kmod_unref(ctx->kmod_ctx); daxctl_unref(ctx->daxctl_ctx); info(ctx, "context %p released\n", ctx); free(ctx); } /** * cxl_set_log_fn - override default log routine * @ctx: cxl library context * @log_fn: function to be called for logging messages * * The built-in logging writes to stderr. It can be overridden by a * custom function, to plug log messages into the user's logging * functionality. */ CXL_EXPORT void cxl_set_log_fn(struct cxl_ctx *ctx, void (*cxl_log_fn)(struct cxl_ctx *ctx, int priority, const char *file, int line, const char *fn, const char *format, va_list args)) { ctx->ctx.log_fn = (log_fn) cxl_log_fn; info(ctx, "custom logging function %p registered\n", cxl_log_fn); } /** * cxl_get_log_priority - retrieve current library loglevel (syslog) * @ctx: cxl library context */ CXL_EXPORT int cxl_get_log_priority(struct cxl_ctx *ctx) { return ctx->ctx.log_priority; } /** * cxl_set_log_priority - set log verbosity * @priority: from syslog.h, LOG_ERR, LOG_INFO, LOG_DEBUG * * Note: LOG_DEBUG requires library be built with "configure --enable-debug" */ CXL_EXPORT void cxl_set_log_priority(struct cxl_ctx *ctx, int priority) { ctx->ctx.log_priority = priority; } static int is_enabled(const char *drvpath) { struct stat st; if (lstat(drvpath, &st) < 0 || !S_ISLNK(st.st_mode)) return 0; else return 1; } CXL_EXPORT int cxl_region_is_enabled(struct cxl_region *region) { struct cxl_ctx *ctx = cxl_region_get_ctx(region); char *path = region->dev_buf; int len = region->buf_len; if (snprintf(path, len, "%s/driver", region->dev_path) >= len) { err(ctx, "%s: buffer too small!\n", cxl_region_get_devname(region)); return 0; } return is_enabled(path); } CXL_EXPORT bool cxl_region_qos_class_mismatch(struct cxl_region *region) { struct cxl_decoder *root_decoder = cxl_region_get_decoder(region); struct cxl_memdev_mapping *mapping; cxl_mapping_foreach(region, mapping) { struct cxl_decoder *decoder; struct cxl_memdev *memdev; decoder = cxl_mapping_get_decoder(mapping); if (!decoder) continue; memdev = cxl_decoder_get_memdev(decoder); if (!memdev) continue; if (region->mode == CXL_DECODER_MODE_RAM) { if (root_decoder->qos_class != memdev->ram_qos_class) return true; } else if (region->mode == CXL_DECODER_MODE_PMEM) { if (root_decoder->qos_class != memdev->pmem_qos_class) return true; } } return false; } CXL_EXPORT int cxl_region_disable(struct cxl_region *region) { const char *devname = cxl_region_get_devname(region); struct cxl_ctx *ctx = cxl_region_get_ctx(region); util_unbind(region->dev_path, ctx); if (cxl_region_is_enabled(region)) { err(ctx, "%s: failed to disable\n", devname); return -EBUSY; } dbg(ctx, "%s: disabled\n", devname); return 0; } CXL_EXPORT int cxl_region_enable(struct cxl_region *region) { const char *devname = cxl_region_get_devname(region); struct cxl_ctx *ctx = cxl_region_get_ctx(region); char *path = region->dev_buf; int len = region->buf_len; char buf[SYSFS_ATTR_SIZE]; u64 resource = ULLONG_MAX; if (cxl_region_is_enabled(region)) return 0; util_bind(devname, region->module, "cxl", ctx); if (!cxl_region_is_enabled(region)) { err(ctx, "%s: failed to enable\n", devname); return -ENXIO; } /* * Currently 'resource' is the only attr that may change after enabling. * Just refresh it here. If there are additional resources that need * to be refreshed here later, split these out into a common helper * for this and add_cxl_region() */ if (snprintf(path, len, "%s/resource", region->dev_path) >= len) { err(ctx, "%s: buffer too small!\n", devname); return 0; } if (sysfs_read_attr(ctx, path, buf) == 0) resource = strtoull(buf, NULL, 0); if (resource < ULLONG_MAX) region->start = resource; dbg(ctx, "%s: enabled\n", devname); return 0; } static int cxl_region_delete_name(struct cxl_decoder *decoder, const char *devname) { struct cxl_ctx *ctx = cxl_decoder_get_ctx(decoder); char *path = decoder->dev_buf; int rc; sprintf(path, "%s/delete_region", decoder->dev_path); rc = sysfs_write_attr(ctx, path, devname); if (rc != 0) { err(ctx, "error deleting region: %s\n", strerror(-rc)); return rc; } return 0; } CXL_EXPORT int cxl_region_delete(struct cxl_region *region) { struct cxl_decoder *decoder = cxl_region_get_decoder(region); const char *devname = cxl_region_get_devname(region); int rc; if (cxl_region_is_enabled(region)) return -EBUSY; rc = cxl_region_delete_name(decoder, devname); if (rc != 0) return rc; decoder->regions_init = 0; free_region(region, &decoder->regions); return 0; } static int region_start_cmp(struct cxl_region *r1, struct cxl_region *r2) { if (r1->start == r2->start) return 0; else if (r1->start < r2->start) return -1; else return 1; } static void *add_cxl_region(void *parent, int id, const char *cxlregion_base) { const char *devname = devpath_to_devname(cxlregion_base); char *path = calloc(1, strlen(cxlregion_base) + 100); struct cxl_region *region, *region_dup, *_r; struct cxl_decoder *decoder = parent; struct cxl_ctx *ctx = cxl_decoder_get_ctx(decoder); char buf[SYSFS_ATTR_SIZE]; u64 resource = ULLONG_MAX; dbg(ctx, "%s: base: \'%s\'\n", devname, cxlregion_base); if (!path) return NULL; region = calloc(1, sizeof(*region)); if (!region) goto err_path; region->id = id; region->ctx = ctx; region->decoder = decoder; list_head_init(®ion->mappings); region->dev_path = strdup(cxlregion_base); if (!region->dev_path) goto err; region->dev_buf = calloc(1, strlen(cxlregion_base) + 50); if (!region->dev_buf) goto err; region->buf_len = strlen(cxlregion_base) + 50; sprintf(path, "%s/size", cxlregion_base); if (sysfs_read_attr(ctx, path, buf) < 0) region->size = ULLONG_MAX; else region->size = strtoull(buf, NULL, 0); sprintf(path, "%s/resource", cxlregion_base); if (sysfs_read_attr(ctx, path, buf) == 0) resource = strtoull(buf, NULL, 0); if (resource < ULLONG_MAX) region->start = resource; sprintf(path, "%s/uuid", cxlregion_base); if (sysfs_read_attr(ctx, path, buf) < 0) goto err; if (strlen(buf) && uuid_parse(buf, region->uuid) < 0) { dbg(ctx, "%s:%s\n", path, buf); goto err; } sprintf(path, "%s/interleave_granularity", cxlregion_base); if (sysfs_read_attr(ctx, path, buf) < 0) region->interleave_granularity = UINT_MAX; else region->interleave_granularity = strtoul(buf, NULL, 0); sprintf(path, "%s/interleave_ways", cxlregion_base); if (sysfs_read_attr(ctx, path, buf) < 0) region->interleave_ways = UINT_MAX; else region->interleave_ways = strtoul(buf, NULL, 0); sprintf(path, "%s/commit", cxlregion_base); if (sysfs_read_attr(ctx, path, buf) < 0) region->decode_state = CXL_DECODE_UNKNOWN; else region->decode_state = strtoul(buf, NULL, 0); sprintf(path, "%s/mode", cxlregion_base); if (sysfs_read_attr(ctx, path, buf) < 0) region->mode = CXL_DECODER_MODE_NONE; else region->mode = cxl_decoder_mode_from_ident(buf); sprintf(path, "%s/modalias", cxlregion_base); if (sysfs_read_attr(ctx, path, buf) == 0) region->module = util_modalias_to_module(ctx, buf); cxl_region_foreach_safe(decoder, region_dup, _r) if (region_dup->id == region->id) { list_del_from(&decoder->regions, ®ion_dup->list); list_add_tail(&decoder->stale_regions, ®ion_dup->list); break; } list_add_sorted(&decoder->regions, region, list, region_start_cmp); free(path); return region; err: free(region->dev_path); free(region->dev_buf); free(region); err_path: free(path); return NULL; } static int cxl_flush(struct cxl_ctx *ctx) { return sysfs_write_attr(ctx, "/sys/bus/cxl/flush", "1\n"); } static int cxl_wait_probe(struct cxl_ctx *ctx) { unsigned long tmo = ctx->timeout; int rc, sleep = 0; do { rc = cxl_flush(ctx); if (rc < 0) break; if (udev_queue_get_queue_is_empty(ctx->udev_queue)) break; sleep++; usleep(1000); } while (ctx->timeout == 0 || tmo-- != 0); if (sleep) dbg(ctx, "waited %d millisecond%s...\n", sleep, sleep == 1 ? "" : "s"); return rc < 0 ? -ENXIO : 0; } static int device_parse(struct cxl_ctx *ctx, const char *base_path, const char *dev_name, void *parent, add_dev_fn add_dev) { cxl_wait_probe(ctx); return sysfs_device_parse(ctx, base_path, dev_name, parent, add_dev); } static void cxl_regions_init(struct cxl_decoder *decoder) { struct cxl_port *port = cxl_decoder_get_port(decoder); struct cxl_ctx *ctx = cxl_decoder_get_ctx(decoder); if (decoder->regions_init) return; /* Only root port decoders may have child regions */ if (!cxl_port_is_root(port)) return; decoder->regions_init = 1; device_parse(ctx, decoder->dev_path, "region", decoder, add_cxl_region); } CXL_EXPORT struct cxl_region *cxl_region_get_first(struct cxl_decoder *decoder) { cxl_regions_init(decoder); return list_top(&decoder->regions, struct cxl_region, list); } CXL_EXPORT struct cxl_region *cxl_region_get_next(struct cxl_region *region) { struct cxl_decoder *decoder = region->decoder; return list_next(&decoder->regions, region, list); } CXL_EXPORT struct cxl_ctx *cxl_region_get_ctx(struct cxl_region *region) { return region->ctx; } CXL_EXPORT struct cxl_decoder *cxl_region_get_decoder(struct cxl_region *region) { return region->decoder; } CXL_EXPORT int cxl_region_get_id(struct cxl_region *region) { return region->id; } CXL_EXPORT const char *cxl_region_get_devname(struct cxl_region *region) { return devpath_to_devname(region->dev_path); } CXL_EXPORT void cxl_region_get_uuid(struct cxl_region *region, uuid_t uu) { memcpy(uu, region->uuid, sizeof(uuid_t)); } CXL_EXPORT unsigned long long cxl_region_get_size(struct cxl_region *region) { return region->size; } CXL_EXPORT unsigned long long cxl_region_get_resource(struct cxl_region *region) { return region->start; } CXL_EXPORT enum cxl_decoder_mode cxl_region_get_mode(struct cxl_region *region) { return region->mode; } CXL_EXPORT unsigned int cxl_region_get_interleave_ways(struct cxl_region *region) { return region->interleave_ways; } CXL_EXPORT int cxl_region_decode_is_committed(struct cxl_region *region) { return (region->decode_state == CXL_DECODE_COMMIT) ? 1 : 0; } CXL_EXPORT unsigned int cxl_region_get_interleave_granularity(struct cxl_region *region) { return region->interleave_granularity; } CXL_EXPORT struct cxl_decoder * cxl_region_get_target_decoder(struct cxl_region *region, int position) { const char *devname = cxl_region_get_devname(region); struct cxl_ctx *ctx = cxl_region_get_ctx(region); int len = region->buf_len, rc; char *path = region->dev_buf; struct cxl_decoder *decoder; char buf[SYSFS_ATTR_SIZE]; if (snprintf(path, len, "%s/target%d", region->dev_path, position) >= len) { err(ctx, "%s: buffer too small!\n", devname); return NULL; } rc = sysfs_read_attr(ctx, path, buf); if (rc < 0) { err(ctx, "%s: error reading target%d: %s\n", devname, position, strerror(-rc)); return NULL; } decoder = cxl_decoder_get_by_name(ctx, buf); if (!decoder) { err(ctx, "%s: error locating decoder for target%d\n", devname, position); return NULL; } return decoder; } CXL_EXPORT struct daxctl_region * cxl_region_get_daxctl_region(struct cxl_region *region) { const char *devname = cxl_region_get_devname(region); struct cxl_ctx *ctx = cxl_region_get_ctx(region); char *path = region->dev_buf; int len = region->buf_len; uuid_t uuid = { 0 }; struct stat st; if (region->dax_region) return region->dax_region; if (snprintf(region->dev_buf, len, "%s/dax_region%d", region->dev_path, region->id) >= len) { err(ctx, "%s: buffer too small!\n", devname); return NULL; } if (stat(path, &st) < 0) return NULL; region->dax_region = daxctl_new_region(ctx->daxctl_ctx, region->id, uuid, path); return region->dax_region; } CXL_EXPORT int cxl_region_set_size(struct cxl_region *region, unsigned long long size) { const char *devname = cxl_region_get_devname(region); struct cxl_ctx *ctx = cxl_region_get_ctx(region); int len = region->buf_len, rc; char *path = region->dev_buf; char buf[SYSFS_ATTR_SIZE]; if (size == 0) { dbg(ctx, "%s: cannot use %s to delete a region\n", __func__, devname); return -EINVAL; } if (snprintf(path, len, "%s/size", region->dev_path) >= len) { err(ctx, "%s: buffer too small!\n", devname); return -ENXIO; } sprintf(buf, "%#llx\n", size); rc = sysfs_write_attr(ctx, path, buf); if (rc < 0) return rc; region->size = size; return 0; } CXL_EXPORT int cxl_region_set_uuid(struct cxl_region *region, uuid_t uu) { const char *devname = cxl_region_get_devname(region); struct cxl_ctx *ctx = cxl_region_get_ctx(region); int len = region->buf_len, rc; char *path = region->dev_buf; char uuid[SYSFS_ATTR_SIZE]; if (snprintf(path, len, "%s/uuid", region->dev_path) >= len) { err(ctx, "%s: buffer too small!\n", devname); return -ENXIO; } uuid_unparse(uu, uuid); rc = sysfs_write_attr(ctx, path, uuid); if (rc != 0) return rc; memcpy(region->uuid, uu, sizeof(uuid_t)); return 0; } CXL_EXPORT int cxl_region_set_interleave_ways(struct cxl_region *region, unsigned int ways) { const char *devname = cxl_region_get_devname(region); struct cxl_ctx *ctx = cxl_region_get_ctx(region); int len = region->buf_len, rc; char *path = region->dev_buf; char buf[SYSFS_ATTR_SIZE]; if (snprintf(path, len, "%s/interleave_ways", region->dev_path) >= len) { err(ctx, "%s: buffer too small!\n", devname); return -ENXIO; } sprintf(buf, "%u\n", ways); rc = sysfs_write_attr(ctx, path, buf); if (rc < 0) return rc; region->interleave_ways = ways; return 0; } CXL_EXPORT int cxl_region_set_interleave_granularity(struct cxl_region *region, unsigned int granularity) { const char *devname = cxl_region_get_devname(region); struct cxl_ctx *ctx = cxl_region_get_ctx(region); int len = region->buf_len, rc; char *path = region->dev_buf; char buf[SYSFS_ATTR_SIZE]; if (snprintf(path, len, "%s/interleave_granularity", region->dev_path) >= len) { err(ctx, "%s: buffer too small!\n", devname); return -ENXIO; } sprintf(buf, "%u\n", granularity); rc = sysfs_write_attr(ctx, path, buf); if (rc < 0) return rc; region->interleave_granularity = granularity; return 0; } static int region_write_target(struct cxl_region *region, int position, struct cxl_decoder *decoder) { const char *devname = cxl_region_get_devname(region); struct cxl_ctx *ctx = cxl_region_get_ctx(region); int len = region->buf_len, rc; char *path = region->dev_buf; const char *dec_name = ""; if (decoder) dec_name = cxl_decoder_get_devname(decoder); if (snprintf(path, len, "%s/target%d", region->dev_path, position) >= len) { err(ctx, "%s: buffer too small!\n", devname); return -ENXIO; } rc = sysfs_write_attr(ctx, path, dec_name); if (rc < 0) return rc; return 0; } CXL_EXPORT int cxl_region_set_target(struct cxl_region *region, int position, struct cxl_decoder *decoder) { if (!decoder) return -ENXIO; return region_write_target(region, position, decoder); } CXL_EXPORT int cxl_region_clear_target(struct cxl_region *region, int position) { const char *devname = cxl_region_get_devname(region); struct cxl_ctx *ctx = cxl_region_get_ctx(region); int rc; if (cxl_region_is_enabled(region)) { err(ctx, "%s: can't clear targets on an active region\n", devname); return -EBUSY; } rc = region_write_target(region, position, NULL); if (rc) { err(ctx, "%s: error clearing target%d: %s\n", devname, position, strerror(-rc)); return rc; } return 0; } CXL_EXPORT int cxl_region_clear_all_targets(struct cxl_region *region) { const char *devname = cxl_region_get_devname(region); struct cxl_ctx *ctx = cxl_region_get_ctx(region); unsigned int ways, i; int rc; if (cxl_region_is_enabled(region)) { err(ctx, "%s: can't clear targets on an active region\n", devname); return -EBUSY; } ways = cxl_region_get_interleave_ways(region); if (ways == 0 || ways == UINT_MAX) return -ENXIO; for (i = 0; i < ways; i++) { rc = region_write_target(region, i, NULL); if (rc) { err(ctx, "%s: error clearing target%d: %s\n", devname, i, strerror(-rc)); return rc; } } return 0; } static int set_region_decode(struct cxl_region *region, enum cxl_decode_state decode_state) { const char *devname = cxl_region_get_devname(region); struct cxl_ctx *ctx = cxl_region_get_ctx(region); int len = region->buf_len, rc; char *path = region->dev_buf; char buf[SYSFS_ATTR_SIZE]; if (snprintf(path, len, "%s/commit", region->dev_path) >= len) { err(ctx, "%s: buffer too small!\n", devname); return -ENXIO; } sprintf(buf, "%d\n", decode_state); rc = sysfs_write_attr(ctx, path, buf); if (rc < 0) return rc; region->decode_state = decode_state; return 0; } CXL_EXPORT int cxl_region_decode_commit(struct cxl_region *region) { return set_region_decode(region, CXL_DECODE_COMMIT); } CXL_EXPORT int cxl_region_decode_reset(struct cxl_region *region) { return set_region_decode(region, CXL_DECODE_RESET); } static struct cxl_decoder *__cxl_port_match_decoder(struct cxl_port *port, const char *ident) { struct cxl_decoder *decoder; cxl_decoder_foreach(port, decoder) if (strcmp(cxl_decoder_get_devname(decoder), ident) == 0) return decoder; return NULL; } static struct cxl_decoder *cxl_port_find_decoder(struct cxl_port *port, const char *ident) { struct cxl_decoder *decoder; struct cxl_endpoint *ep; /* First, check decoders directly under @port */ decoder = __cxl_port_match_decoder(port, ident); if (decoder) return decoder; /* Next, iterate over the endpoints under @port */ cxl_endpoint_foreach(port, ep) { decoder = __cxl_port_match_decoder(cxl_endpoint_get_port(ep), ident); if (decoder) return decoder; } return NULL; } CXL_EXPORT struct cxl_decoder *cxl_decoder_get_by_name(struct cxl_ctx *ctx, const char *ident) { struct cxl_bus *bus; cxl_bus_foreach(ctx, bus) { struct cxl_decoder *decoder; struct cxl_port *port, *top; port = cxl_bus_get_port(bus); decoder = cxl_port_find_decoder(port, ident); if (decoder) return decoder; top = port; cxl_port_foreach_all (top, port) { decoder = cxl_port_find_decoder(port, ident); if (decoder) return decoder; } } return NULL; } static void cxl_mappings_init(struct cxl_region *region) { const char *devname = cxl_region_get_devname(region); struct cxl_ctx *ctx = cxl_region_get_ctx(region); char *mapping_path, buf[SYSFS_ATTR_SIZE]; unsigned int i; if (region->mappings_init) return; region->mappings_init = 1; mapping_path = calloc(1, strlen(region->dev_path) + 100); if (!mapping_path) { err(ctx, "%s: allocation failure\n", devname); return; } for (i = 0; i < region->interleave_ways; i++) { struct cxl_memdev_mapping *mapping; struct cxl_decoder *decoder; sprintf(mapping_path, "%s/target%d", region->dev_path, i); if (sysfs_read_attr(ctx, mapping_path, buf) < 0) { err(ctx, "%s: failed to read target%d\n", devname, i); continue; } decoder = cxl_decoder_get_by_name(ctx, buf); if (!decoder) { err(ctx, "%s target%d: %s lookup failure\n", devname, i, buf); continue; } mapping = calloc(1, sizeof(*mapping)); if (!mapping) { err(ctx, "%s target%d: allocation failure\n", devname, i); continue; } mapping->region = region; mapping->decoder = decoder; mapping->position = i; list_add(®ion->mappings, &mapping->list); } free(mapping_path); } CXL_EXPORT struct cxl_memdev_mapping * cxl_mapping_get_first(struct cxl_region *region) { cxl_mappings_init(region); return list_top(®ion->mappings, struct cxl_memdev_mapping, list); } CXL_EXPORT struct cxl_memdev_mapping * cxl_mapping_get_next(struct cxl_memdev_mapping *mapping) { struct cxl_region *region = mapping->region; return list_next(®ion->mappings, mapping, list); } CXL_EXPORT struct cxl_decoder * cxl_mapping_get_decoder(struct cxl_memdev_mapping *mapping) { return mapping->decoder; } CXL_EXPORT unsigned int cxl_mapping_get_position(struct cxl_memdev_mapping *mapping) { return mapping->position; } static void *add_cxl_pmem(void *parent, int id, const char *br_base) { const char *devname = devpath_to_devname(br_base); struct cxl_memdev *memdev = parent; struct cxl_ctx *ctx = memdev->ctx; struct cxl_pmem *pmem; dbg(ctx, "%s: pmem_base: \'%s\'\n", devname, br_base); pmem = calloc(1, sizeof(*pmem)); if (!pmem) goto err_dev; pmem->id = id; pmem->dev_path = strdup(br_base); if (!pmem->dev_path) goto err_read; pmem->dev_buf = calloc(1, strlen(br_base) + 50); if (!pmem->dev_buf) goto err_read; pmem->buf_len = strlen(br_base) + 50; memdev->pmem = pmem; return pmem; err_read: free(pmem->dev_buf); free(pmem->dev_path); free(pmem); err_dev: return NULL; } static int add_cxl_memdev_fwl(struct cxl_memdev *memdev, const char *cxlmem_base) { const char *devname = cxl_memdev_get_devname(memdev); struct cxl_fw_loader *fwl; fwl = calloc(1, sizeof(*fwl)); if (!fwl) return -ENOMEM; if (asprintf(&fwl->loading, "%s/firmware/%s/loading", cxlmem_base, devname) < 0) goto err_read; if (asprintf(&fwl->data, "%s/firmware/%s/data", cxlmem_base, devname) < 0) goto err_read; if (asprintf(&fwl->remaining, "%s/firmware/%s/remaining_size", cxlmem_base, devname) < 0) goto err_read; if (asprintf(&fwl->cancel, "%s/firmware/%s/cancel", cxlmem_base, devname) < 0) goto err_read; if (asprintf(&fwl->status, "%s/firmware/%s/status", cxlmem_base, devname) < 0) goto err_read; memdev->fwl = fwl; return 0; err_read: free_fwl(fwl); return -ENOMEM; } static void *add_cxl_memdev(void *parent, int id, const char *cxlmem_base) { const char *devname = devpath_to_devname(cxlmem_base); char *path = calloc(1, strlen(cxlmem_base) + 100); struct cxl_ctx *ctx = parent; struct cxl_memdev *memdev, *memdev_dup; char buf[SYSFS_ATTR_SIZE]; struct stat st; char *host; if (!path) return NULL; dbg(ctx, "%s: base: \'%s\'\n", devname, cxlmem_base); memdev = calloc(1, sizeof(*memdev)); if (!memdev) goto err_dev; memdev->id = id; memdev->ctx = ctx; sprintf(path, "/dev/cxl/%s", devname); if (stat(path, &st) < 0) goto err_read; memdev->major = major(st.st_rdev); memdev->minor = minor(st.st_rdev); sprintf(path, "%s/pmem/size", cxlmem_base); if (sysfs_read_attr(ctx, path, buf) == 0) memdev->pmem_size = strtoull(buf, NULL, 0); sprintf(path, "%s/ram/size", cxlmem_base); if (sysfs_read_attr(ctx, path, buf) == 0) memdev->ram_size = strtoull(buf, NULL, 0); sprintf(path, "%s/pmem/qos_class", cxlmem_base); if (sysfs_read_attr(ctx, path, buf) < 0) memdev->pmem_qos_class = CXL_QOS_CLASS_NONE; else memdev->pmem_qos_class = atoi(buf); sprintf(path, "%s/ram/qos_class", cxlmem_base); if (sysfs_read_attr(ctx, path, buf) < 0) memdev->ram_qos_class = CXL_QOS_CLASS_NONE; else memdev->ram_qos_class = atoi(buf); sprintf(path, "%s/payload_max", cxlmem_base); if (sysfs_read_attr(ctx, path, buf) == 0) { memdev->payload_max = strtoull(buf, NULL, 0); if (memdev->payload_max < 0) goto err_read; } else { memdev->payload_max = -1; } sprintf(path, "%s/label_storage_size", cxlmem_base); if (sysfs_read_attr(ctx, path, buf) == 0) { memdev->lsa_size = strtoull(buf, NULL, 0); if (memdev->lsa_size == ULLONG_MAX) goto err_read; } else { memdev->lsa_size = SIZE_MAX; } sprintf(path, "%s/serial", cxlmem_base); if (sysfs_read_attr(ctx, path, buf) < 0) memdev->serial = ULLONG_MAX; else memdev->serial = strtoull(buf, NULL, 0); sprintf(path, "%s/numa_node", cxlmem_base); if (sysfs_read_attr(ctx, path, buf) < 0) memdev->numa_node = -1; else memdev->numa_node = strtol(buf, NULL, 0); memdev->dev_path = strdup(cxlmem_base); if (!memdev->dev_path) goto err_read; memdev->host_path = realpath(cxlmem_base, NULL); if (!memdev->host_path) goto err_read; host = strrchr(memdev->host_path, '/'); if (!host) goto err_read; host[0] = '\0'; sprintf(path, "%s/firmware_version", cxlmem_base); if (sysfs_read_attr(ctx, path, buf) == 0) { memdev->firmware_version = strdup(buf); if (!memdev->firmware_version) goto err_read; } memdev->dev_buf = calloc(1, strlen(cxlmem_base) + 50); if (!memdev->dev_buf) goto err_read; memdev->buf_len = strlen(cxlmem_base) + 50; device_parse(ctx, cxlmem_base, "pmem", memdev, add_cxl_pmem); if (add_cxl_memdev_fwl(memdev, cxlmem_base)) goto err_read; cxl_memdev_foreach(ctx, memdev_dup) if (memdev_dup->id == memdev->id) { free_memdev(memdev, NULL); free(path); return memdev_dup; } list_add(&ctx->memdevs, &memdev->list); free(path); return memdev; err_read: free(memdev->firmware_version); free(memdev->dev_buf); free(memdev->dev_path); free(memdev->host_path); free(memdev); err_dev: free(path); return NULL; } static void cxl_memdevs_init(struct cxl_ctx *ctx) { if (ctx->memdevs_init) return; ctx->memdevs_init = 1; device_parse(ctx, "/sys/bus/cxl/devices", "mem", ctx, add_cxl_memdev); } CXL_EXPORT struct cxl_ctx *cxl_memdev_get_ctx(struct cxl_memdev *memdev) { return memdev->ctx; } CXL_EXPORT struct cxl_memdev *cxl_memdev_get_first(struct cxl_ctx *ctx) { cxl_memdevs_init(ctx); return list_top(&ctx->memdevs, struct cxl_memdev, list); } CXL_EXPORT struct cxl_memdev *cxl_memdev_get_next(struct cxl_memdev *memdev) { struct cxl_ctx *ctx = memdev->ctx; return list_next(&ctx->memdevs, memdev, list); } CXL_EXPORT int cxl_memdev_get_id(struct cxl_memdev *memdev) { return memdev->id; } CXL_EXPORT int cxl_memdev_wait_sanitize(struct cxl_memdev *memdev, int timeout_ms) { struct cxl_ctx *ctx = cxl_memdev_get_ctx(memdev); const char *devname = cxl_memdev_get_devname(memdev); char *path = memdev->dev_buf; int len = memdev->buf_len; struct pollfd fds = { 0 }; int fd = 0, rc; char buf[9]; if (snprintf(path, len, "%s/security/state", memdev->dev_path) >= len) { err(ctx, "%s: buffer too small!\n", devname); return -ERANGE; } fd = open(path, O_RDONLY|O_CLOEXEC); if (fd < 0) { /* device does not support security operations */ if (errno == ENOENT) return 0; rc = -errno; err(ctx, "%s: %s\n", path, strerror(errno)); return rc; } memset(&fds, 0, sizeof(fds)); fds.fd = fd; rc = pread(fd, buf, sizeof(buf), 0); if (rc < 0) { rc = -EOPNOTSUPP; goto out; } /* skipping if not currently sanitizing */ if (strncmp(buf, "sanitize", 8) != 0) { rc = 0; goto out; } rc = poll(&fds, 1, timeout_ms); if (rc == 0) { dbg(ctx, "%s: sanitize timeout\n", devname); rc = -ETIMEDOUT; } else if (rc < 0) { err(ctx, "%s: sanitize poll error: %s\n", devname, strerror(errno)); rc = -errno; } else { dbg(ctx, "%s: sanitize wake\n", devname); rc = pread(fd, buf, sizeof(buf), 0); if (rc < 0 || strncmp(buf, "sanitize", 8) == 0) { err(ctx, "%s: sanitize wake error\n", devname); rc = -ENXIO; } else rc = 0; } out: close(fd); return rc; } CXL_EXPORT unsigned long long cxl_memdev_get_serial(struct cxl_memdev *memdev) { return memdev->serial; } CXL_EXPORT int cxl_memdev_get_numa_node(struct cxl_memdev *memdev) { return memdev->numa_node; } CXL_EXPORT const char *cxl_memdev_get_devname(struct cxl_memdev *memdev) { return devpath_to_devname(memdev->dev_path); } CXL_EXPORT const char *cxl_memdev_get_host(struct cxl_memdev *memdev) { return devpath_to_devname(memdev->host_path); } CXL_EXPORT struct cxl_bus *cxl_memdev_get_bus(struct cxl_memdev *memdev) { struct cxl_endpoint *endpoint = cxl_memdev_get_endpoint(memdev); if (!endpoint) return NULL; return cxl_endpoint_get_bus(endpoint); } CXL_EXPORT int cxl_memdev_get_major(struct cxl_memdev *memdev) { return memdev->major; } CXL_EXPORT int cxl_memdev_get_minor(struct cxl_memdev *memdev) { return memdev->minor; } CXL_EXPORT unsigned long long cxl_memdev_get_pmem_size(struct cxl_memdev *memdev) { return memdev->pmem_size; } CXL_EXPORT unsigned long long cxl_memdev_get_ram_size(struct cxl_memdev *memdev) { return memdev->ram_size; } CXL_EXPORT int cxl_memdev_get_pmem_qos_class(struct cxl_memdev *memdev) { return memdev->pmem_qos_class; } CXL_EXPORT int cxl_memdev_get_ram_qos_class(struct cxl_memdev *memdev) { return memdev->ram_qos_class; } CXL_EXPORT const char *cxl_memdev_get_firmware_verison(struct cxl_memdev *memdev) { return memdev->firmware_version; } static enum cxl_fwl_status cxl_fwl_get_status(struct cxl_memdev *memdev) { const char *devname = cxl_memdev_get_devname(memdev); struct cxl_ctx *ctx = cxl_memdev_get_ctx(memdev); struct cxl_fw_loader *fwl = memdev->fwl; char buf[SYSFS_ATTR_SIZE]; int rc; rc = sysfs_read_attr(ctx, fwl->status, buf); if (rc < 0) { err(ctx, "%s: failed to get fw loader status (%s)\n", devname, strerror(-rc)); return CXL_FWL_STATUS_UNKNOWN; } return cxl_fwl_status_from_ident(buf); } CXL_EXPORT bool cxl_memdev_fw_update_in_progress(struct cxl_memdev *memdev) { int status = cxl_fwl_get_status(memdev); if (status == CXL_FWL_STATUS_IDLE) return false; return true; } CXL_EXPORT size_t cxl_memdev_fw_update_get_remaining(struct cxl_memdev *memdev) { const char *devname = cxl_memdev_get_devname(memdev); struct cxl_ctx *ctx = cxl_memdev_get_ctx(memdev); struct cxl_fw_loader *fwl = memdev->fwl; char buf[SYSFS_ATTR_SIZE]; int rc; rc = sysfs_read_attr(ctx, fwl->remaining, buf); if (rc < 0) { err(ctx, "%s: failed to get fw loader remaining size (%s)\n", devname, strerror(-rc)); return 0; } return strtoull(buf, NULL, 0); } static int cxl_memdev_fwl_set_loading(struct cxl_memdev *memdev, enum cxl_fwl_loading loadval) { const char *devname = cxl_memdev_get_devname(memdev); struct cxl_ctx *ctx = cxl_memdev_get_ctx(memdev); struct cxl_fw_loader *fwl = memdev->fwl; char buf[SYSFS_ATTR_SIZE]; int rc; sprintf(buf, "%d\n", loadval); rc = sysfs_write_attr(ctx, fwl->loading, buf); if (rc < 0) { err(ctx, "%s: failed to trigger fw loading to %d (%s)\n", devname, loadval, strerror(-rc)); return rc; } return 0; } static int cxl_memdev_fwl_copy_data(struct cxl_memdev *memdev, void *fw_buf, size_t size) { struct cxl_ctx *ctx = cxl_memdev_get_ctx(memdev); struct cxl_fw_loader *fwl = memdev->fwl; FILE *fwl_data; size_t rw_len; int rc = 0; fwl_data = fopen(fwl->data, "w"); if (!fwl_data) { err(ctx, "failed to open: %s: (%s)\n", fwl->data, strerror(errno)); return -errno; } rw_len = fwrite(fw_buf, 1, size, fwl_data); if (rw_len != size) { rc = -ENXIO; goto out_close; } fflush(fwl_data); out_close: fclose(fwl_data); return rc; } CXL_EXPORT int cxl_memdev_update_fw(struct cxl_memdev *memdev, const char *fw_path) { struct cxl_ctx *ctx = cxl_memdev_get_ctx(memdev); struct stat s; int f_in, rc; void *fw_buf; f_in = open(fw_path, O_RDONLY); if (f_in < 0) { err(ctx, "failed to open: %s: (%s)\n", fw_path, strerror(errno)); return -errno; } rc = fstat(f_in, &s); if (rc < 0) { err(ctx, "failed to stat: %s: (%s)\n", fw_path, strerror(errno)); rc = -errno; goto out_close; } fw_buf = mmap(NULL, s.st_size, PROT_READ, MAP_PRIVATE, f_in, 0); if (fw_buf == MAP_FAILED) { err(ctx, "failed to map: %s: (%s)\n", fw_path, strerror(errno)); rc = -errno; goto out_close; } rc = cxl_memdev_fwl_set_loading(memdev, CXL_FWL_LOADING_START); if (rc) goto out_unmap; rc = cxl_memdev_fwl_copy_data(memdev, fw_buf, s.st_size); if (rc) goto out_unmap; rc = cxl_memdev_fwl_set_loading(memdev, CXL_FWL_LOADING_END); out_unmap: munmap(fw_buf, s.st_size); out_close: close(f_in); return rc; } CXL_EXPORT int cxl_memdev_cancel_fw_update(struct cxl_memdev *memdev) { struct cxl_ctx *ctx = cxl_memdev_get_ctx(memdev); struct cxl_fw_loader *fwl = memdev->fwl; int rc; if (!cxl_memdev_fw_update_in_progress(memdev) && cxl_memdev_fw_update_get_remaining(memdev) == 0) return -ENXIO; rc = sysfs_write_attr(ctx, fwl->cancel, "1\n"); if (rc < 0) return rc; return 0; } static void bus_invalidate(struct cxl_bus *bus) { struct cxl_ctx *ctx = cxl_bus_get_ctx(bus); struct cxl_port *bus_port, *port, *_p; struct cxl_memdev *memdev; /* * Something happend to cause the state of all ports to be * indeterminate, delete them all and start over. */ cxl_memdev_foreach(ctx, memdev) memdev->endpoint = NULL; bus_port = cxl_bus_get_port(bus); list_for_each_safe(&bus_port->child_ports, port, _p, list) free_port(port, &bus_port->child_ports); bus_port->ports_init = 0; cxl_flush(ctx); } CXL_EXPORT int cxl_bus_disable_invalidate(struct cxl_bus *bus) { struct cxl_ctx *ctx = cxl_bus_get_ctx(bus); struct cxl_port *port = cxl_bus_get_port(bus); int rc; rc = util_unbind(port->uport, ctx); if (rc) return rc; free_bus(bus, &ctx->buses); cxl_flush(ctx); return 0; } CXL_EXPORT int cxl_memdev_disable_invalidate(struct cxl_memdev *memdev) { struct cxl_ctx *ctx = cxl_memdev_get_ctx(memdev); const char *devname = cxl_memdev_get_devname(memdev); struct cxl_bus *bus; if (!cxl_memdev_is_enabled(memdev)) return 0; bus = cxl_memdev_get_bus(memdev); if (!bus) { err(ctx, "%s: failed to invalidate\n", devname); return -ENXIO; } util_unbind(memdev->dev_path, ctx); if (cxl_memdev_is_enabled(memdev)) { err(ctx, "%s: failed to disable\n", devname); return -EBUSY; } bus_invalidate(bus); dbg(ctx, "%s: disabled\n", devname); return 0; } CXL_EXPORT int cxl_memdev_trigger_poison_list(struct cxl_memdev *memdev) { struct cxl_ctx *ctx = cxl_memdev_get_ctx(memdev); char *path = memdev->dev_buf; int len = memdev->buf_len, rc; if (snprintf(path, len, "%s/trigger_poison_list", memdev->dev_path) >= len) { err(ctx, "%s: buffer too small\n", cxl_memdev_get_devname(memdev)); return -ENXIO; } if (access(path, F_OK) != 0) { err(ctx, "%s: trigger_poison_list unsupported by device\n", cxl_memdev_get_devname(memdev)); return -ENXIO; } rc = sysfs_write_attr(ctx, path, "1\n"); if (rc < 0) { err(ctx, "%s: Failed trigger_poison_list\n", cxl_memdev_get_devname(memdev)); return rc; } return 0; } CXL_EXPORT int cxl_region_trigger_poison_list(struct cxl_region *region) { struct cxl_memdev_mapping *mapping; int rc; cxl_mapping_foreach(region, mapping) { struct cxl_decoder *decoder; struct cxl_memdev *memdev; decoder = cxl_mapping_get_decoder(mapping); if (!decoder) continue; memdev = cxl_decoder_get_memdev(decoder); if (!memdev) continue; rc = cxl_memdev_trigger_poison_list(memdev); if (rc) return rc; } return 0; } CXL_EXPORT int cxl_memdev_enable(struct cxl_memdev *memdev) { struct cxl_ctx *ctx = cxl_memdev_get_ctx(memdev); const char *devname = cxl_memdev_get_devname(memdev); if (cxl_memdev_is_enabled(memdev)) return 0; util_bind(devname, memdev->module, "cxl", ctx); if (!cxl_memdev_is_enabled(memdev)) { err(ctx, "%s: failed to enable\n", devname); return -ENXIO; } dbg(ctx, "%s: enabled\n", devname); return 0; } static struct cxl_endpoint * cxl_port_recurse_endpoint(struct cxl_port *parent_port, struct cxl_memdev *memdev) { struct cxl_endpoint *endpoint; struct cxl_port *port; cxl_port_foreach(parent_port, port) { cxl_endpoint_foreach(port, endpoint) if (strcmp(cxl_endpoint_get_host(endpoint), cxl_memdev_get_devname(memdev)) == 0) return endpoint; endpoint = cxl_port_recurse_endpoint(port, memdev); if (endpoint) return endpoint; } return NULL; } static struct cxl_endpoint *cxl_port_find_endpoint(struct cxl_port *parent_port, struct cxl_memdev *memdev) { struct cxl_endpoint *endpoint; cxl_endpoint_foreach(parent_port, endpoint) if (strcmp(cxl_endpoint_get_host(endpoint), cxl_memdev_get_devname(memdev)) == 0) return endpoint; return cxl_port_recurse_endpoint(parent_port, memdev); } CXL_EXPORT struct cxl_endpoint * cxl_memdev_get_endpoint(struct cxl_memdev *memdev) { struct cxl_ctx *ctx = cxl_memdev_get_ctx(memdev); struct cxl_endpoint *endpoint = NULL; struct cxl_bus *bus; if (memdev->endpoint) return memdev->endpoint; if (!cxl_memdev_is_enabled(memdev)) return NULL; cxl_bus_foreach (ctx, bus) { struct cxl_port *port = cxl_bus_get_port(bus); endpoint = cxl_port_find_endpoint(port, memdev); if (endpoint) break; } if (!endpoint) return NULL; if (endpoint->memdev && endpoint->memdev != memdev) err(ctx, "%s assigned to %s not %s\n", cxl_endpoint_get_devname(endpoint), cxl_memdev_get_devname(endpoint->memdev), cxl_memdev_get_devname(memdev)); memdev->endpoint = endpoint; endpoint->memdev = memdev; return endpoint; } CXL_EXPORT size_t cxl_memdev_get_label_size(struct cxl_memdev *memdev) { return memdev->lsa_size; } CXL_EXPORT int cxl_memdev_is_enabled(struct cxl_memdev *memdev) { struct cxl_ctx *ctx = cxl_memdev_get_ctx(memdev); char *path = memdev->dev_buf; int len = memdev->buf_len; if (snprintf(path, len, "%s/driver", memdev->dev_path) >= len) { err(ctx, "%s: buffer too small!\n", cxl_memdev_get_devname(memdev)); return 0; } return is_enabled(path); } CXL_EXPORT int cxl_memdev_nvdimm_bridge_active(struct cxl_memdev *memdev) { struct cxl_ctx *ctx = cxl_memdev_get_ctx(memdev); struct cxl_pmem *pmem = memdev->pmem; char *path; int len; if (!pmem) return 0; path = pmem->dev_buf; len = pmem->buf_len; if (snprintf(path, len, "%s/driver", pmem->dev_path) >= len) { err(ctx, "%s: nvdimm pmem buffer too small!\n", cxl_memdev_get_devname(memdev)); return 0; } return is_enabled(path); } static int cxl_port_init(struct cxl_port *port, struct cxl_port *parent_port, enum cxl_port_type type, struct cxl_ctx *ctx, int id, const char *cxlport_base) { char *path = calloc(1, strlen(cxlport_base) + 100); char buf[SYSFS_ATTR_SIZE]; size_t rc; if (!path) return -ENOMEM; port->id = id; port->ctx = ctx; port->type = type; port->parent = parent_port; port->type = type; port->depth = parent_port ? parent_port->depth + 1 : 0; list_head_init(&port->child_ports); list_head_init(&port->endpoints); list_head_init(&port->decoders); list_head_init(&port->dports); port->dev_path = strdup(cxlport_base); if (!port->dev_path) goto err; port->dev_buf = calloc(1, strlen(cxlport_base) + 50); if (!port->dev_buf) goto err; port->buf_len = strlen(cxlport_base) + 50; rc = snprintf(port->dev_buf, port->buf_len, "%s/uport", cxlport_base); if (rc >= port->buf_len) goto err; port->uport = realpath(port->dev_buf, NULL); if (!port->uport) goto err; /* * CXL root devices have no parents and level 1 ports are both * CXL root targets and hosts of the next level, so: * parent_dport == uport * ...at depth == 1 */ if (port->depth > 1) { rc = snprintf(port->dev_buf, port->buf_len, "%s/parent_dport", cxlport_base); if (rc >= port->buf_len) goto err; port->parent_dport_path = realpath(port->dev_buf, NULL); } sprintf(path, "%s/modalias", cxlport_base); if (sysfs_read_attr(ctx, path, buf) == 0) port->module = util_modalias_to_module(ctx, buf); sprintf(path, "%s/decoders_committed", cxlport_base); if (sysfs_read_attr(ctx, path, buf) == 0) port->decoders_committed = strtoul(buf, NULL, 0); free(path); return 0; err: free(port->dev_path); free(port->dev_buf); free(path); return -ENOMEM; } static void *add_cxl_endpoint(void *parent, int id, const char *cxlep_base) { const char *devname = devpath_to_devname(cxlep_base); struct cxl_endpoint *endpoint, *endpoint_dup; struct cxl_port *port = parent; struct cxl_ctx *ctx = cxl_port_get_ctx(port); int rc; dbg(ctx, "%s: base: \'%s\'\n", devname, cxlep_base); endpoint = calloc(1, sizeof(*endpoint)); if (!endpoint) return NULL; rc = cxl_port_init(&endpoint->port, port, CXL_PORT_ENDPOINT, ctx, id, cxlep_base); if (rc) goto err; cxl_endpoint_foreach(port, endpoint_dup) if (endpoint_dup->port.id == endpoint->port.id) { free_endpoint(endpoint, NULL); return endpoint_dup; } list_add(&port->endpoints, &endpoint->port.list); return endpoint; err: free(endpoint); return NULL; } static void cxl_endpoints_init(struct cxl_port *port) { struct cxl_ctx *ctx = cxl_port_get_ctx(port); if (port->endpoints_init) return; port->endpoints_init = 1; device_parse(ctx, port->dev_path, "endpoint", port, add_cxl_endpoint); } CXL_EXPORT struct cxl_ctx *cxl_endpoint_get_ctx(struct cxl_endpoint *endpoint) { return endpoint->port.ctx; } CXL_EXPORT struct cxl_endpoint *cxl_endpoint_get_first(struct cxl_port *port) { cxl_endpoints_init(port); return list_top(&port->endpoints, struct cxl_endpoint, port.list); } CXL_EXPORT struct cxl_endpoint *cxl_endpoint_get_next(struct cxl_endpoint *endpoint) { struct cxl_port *port = endpoint->port.parent; return list_next(&port->endpoints, endpoint, port.list); } CXL_EXPORT const char *cxl_endpoint_get_devname(struct cxl_endpoint *endpoint) { return devpath_to_devname(endpoint->port.dev_path); } CXL_EXPORT int cxl_endpoint_get_id(struct cxl_endpoint *endpoint) { return endpoint->port.id; } CXL_EXPORT struct cxl_port *cxl_endpoint_get_parent(struct cxl_endpoint *endpoint) { return endpoint->port.parent; } CXL_EXPORT struct cxl_port *cxl_endpoint_get_port(struct cxl_endpoint *endpoint) { return &endpoint->port; } CXL_EXPORT const char *cxl_endpoint_get_host(struct cxl_endpoint *endpoint) { return cxl_port_get_host(&endpoint->port); } CXL_EXPORT struct cxl_bus *cxl_endpoint_get_bus(struct cxl_endpoint *endpoint) { struct cxl_port *port = &endpoint->port; return cxl_port_get_bus(port); } CXL_EXPORT int cxl_endpoint_is_enabled(struct cxl_endpoint *endpoint) { return cxl_port_is_enabled(&endpoint->port); } CXL_EXPORT struct cxl_memdev * cxl_endpoint_get_memdev(struct cxl_endpoint *endpoint) { struct cxl_ctx *ctx = cxl_endpoint_get_ctx(endpoint); struct cxl_memdev *memdev; if (endpoint->memdev) return endpoint->memdev; if (!cxl_endpoint_is_enabled(endpoint)) return NULL; cxl_memdev_foreach(ctx, memdev) if (strcmp(cxl_memdev_get_devname(memdev), cxl_endpoint_get_host(endpoint)) == 0) { if (memdev->endpoint && memdev->endpoint != endpoint) err(ctx, "%s assigned to %s not %s\n", cxl_memdev_get_devname(memdev), cxl_endpoint_get_devname(memdev->endpoint), cxl_endpoint_get_devname(endpoint)); endpoint->memdev = memdev; memdev->endpoint = endpoint; return memdev; } return NULL; } static bool cxl_region_is_configured(struct cxl_region *region) { return region->size && (region->decode_state != CXL_DECODE_RESET); } /** * cxl_decoder_calc_max_available_extent() - calculate max available free space * @decoder - the root decoder to calculate the free extents for * * The add_cxl_region() function adds regions to the parent decoder's list * sorted by the region's start HPAs. It can also be assumed that regions have * no overlapped / aliased HPA space. Therefore, calculating each extent is as * simple as walking the region list in order, and subtracting the previous * region's end HPA from the next region's start HPA (and taking into account * the decoder's start and end HPAs as well). */ static unsigned long long cxl_decoder_calc_max_available_extent(struct cxl_decoder *decoder) { u64 prev_end, decoder_end, cur_extent, max_extent = 0; struct cxl_port *port = cxl_decoder_get_port(decoder); struct cxl_ctx *ctx = cxl_decoder_get_ctx(decoder); struct cxl_region *region; if (!cxl_port_is_root(port)) { err(ctx, "%s: not a root decoder\n", cxl_decoder_get_devname(decoder)); return ULLONG_MAX; } if (decoder->start == ULLONG_MAX) return ULLONG_MAX; /* * Preload prev_end with an imaginary region that ends just before * the decoder's start, so that the extent calculation for the * first region Just Works */ prev_end = decoder->start - 1; cxl_region_foreach(decoder, region) { if (!cxl_region_is_configured(region)) continue; /* * region->start - prev_end would get the difference in * addresses, but a difference of 1 in addresses implies * an extent of 0. Hence the '-1'. */ cur_extent = region->start - prev_end - 1; max_extent = max(max_extent, cur_extent); prev_end = region->start + region->size - 1; } /* * Finally, consider the extent after the last region, up to the end * of the decoder's address space, if any. If there were no regions, * this simply reduces to decoder->size. * Subtracting two addrs gets us a 'size' directly, no need for +/- 1. */ decoder_end = decoder->start + decoder->size - 1; cur_extent = decoder_end - prev_end; max_extent = max(max_extent, cur_extent); return max_extent; } static int decoder_id_cmp(struct cxl_decoder *d1, struct cxl_decoder *d2) { return d1->id - d2->id; } static void *add_cxl_decoder(void *parent, int id, const char *cxldecoder_base) { const char *devname = devpath_to_devname(cxldecoder_base); char *path = calloc(1, strlen(cxldecoder_base) + 100); struct cxl_decoder *decoder, *decoder_dup; struct cxl_port *port = parent; struct cxl_ctx *ctx = cxl_port_get_ctx(port); char buf[SYSFS_ATTR_SIZE]; char *target_id, *save; size_t i; dbg(ctx, "%s: base: \'%s\'\n", devname, cxldecoder_base); if (!path) return NULL; decoder = calloc(1, sizeof(*decoder)); if (!decoder) goto err; decoder->id = id; decoder->ctx = ctx; decoder->port = port; list_head_init(&decoder->targets); list_head_init(&decoder->regions); list_head_init(&decoder->stale_regions); decoder->dev_path = strdup(cxldecoder_base); if (!decoder->dev_path) goto err_decoder; decoder->dev_buf = calloc(1, strlen(cxldecoder_base) + 50); if (!decoder->dev_buf) goto err_decoder; decoder->buf_len = strlen(cxldecoder_base) + 50; sprintf(path, "%s/start", cxldecoder_base); if (sysfs_read_attr(ctx, path, buf) < 0) decoder->start = ULLONG_MAX; else decoder->start = strtoull(buf, NULL, 0); sprintf(path, "%s/size", cxldecoder_base); if (sysfs_read_attr(ctx, path, buf) < 0) decoder->size = ULLONG_MAX; else decoder->size = strtoull(buf, NULL, 0); sprintf(path, "%s/mode", cxldecoder_base); if (sysfs_read_attr(ctx, path, buf) == 0) { if (strcmp(buf, "ram") == 0) decoder->mode = CXL_DECODER_MODE_RAM; else if (strcmp(buf, "pmem") == 0) decoder->mode = CXL_DECODER_MODE_PMEM; else if (strcmp(buf, "mixed") == 0) decoder->mode = CXL_DECODER_MODE_MIXED; else if (strcmp(buf, "none") == 0) decoder->mode = CXL_DECODER_MODE_NONE; else decoder->mode = CXL_DECODER_MODE_MIXED; } else decoder->mode = CXL_DECODER_MODE_NONE; sprintf(path, "%s/interleave_granularity", cxldecoder_base); if (sysfs_read_attr(ctx, path, buf) < 0) decoder->interleave_granularity = UINT_MAX; else decoder->interleave_granularity = strtoul(buf, NULL, 0); sprintf(path, "%s/interleave_ways", cxldecoder_base); if (sysfs_read_attr(ctx, path, buf) < 0) decoder->interleave_ways = UINT_MAX; else decoder->interleave_ways = strtoul(buf, NULL, 0); sprintf(path, "%s/qos_class", cxldecoder_base); if (sysfs_read_attr(ctx, path, buf) < 0) decoder->qos_class = CXL_QOS_CLASS_NONE; else decoder->qos_class = atoi(buf); switch (port->type) { case CXL_PORT_ENDPOINT: sprintf(path, "%s/dpa_resource", cxldecoder_base); if (sysfs_read_attr(ctx, path, buf) < 0) decoder->dpa_resource = ULLONG_MAX; else decoder->dpa_resource = strtoull(buf, NULL, 0); sprintf(path, "%s/dpa_size", cxldecoder_base); if (sysfs_read_attr(ctx, path, buf) < 0) decoder->dpa_size = ULLONG_MAX; else decoder->dpa_size = strtoull(buf, NULL, 0); case CXL_PORT_SWITCH: decoder->pmem_capable = true; decoder->volatile_capable = true; decoder->mem_capable = true; decoder->accelmem_capable = true; sprintf(path, "%s/locked", cxldecoder_base); if (sysfs_read_attr(ctx, path, buf) == 0) decoder->locked = !!strtoul(buf, NULL, 0); sprintf(path, "%s/target_type", cxldecoder_base); if (sysfs_read_attr(ctx, path, buf) == 0) { if (strcmp(buf, "accelerator") == 0) decoder->target_type = CXL_DECODER_TTYPE_ACCELERATOR; if (strcmp(buf, "expander") == 0) decoder->target_type = CXL_DECODER_TTYPE_EXPANDER; } break; case CXL_PORT_ROOT: { struct cxl_decoder_flag { char *name; bool *flag; } flags[] = { { "cap_type2", &decoder->accelmem_capable }, { "cap_type3", &decoder->mem_capable }, { "cap_ram", &decoder->volatile_capable }, { "cap_pmem", &decoder->pmem_capable }, { "locked", &decoder->locked }, }; for (i = 0; i < ARRAY_SIZE(flags); i++) { struct cxl_decoder_flag *flag = &flags[i]; sprintf(path, "%s/%s", cxldecoder_base, flag->name); if (sysfs_read_attr(ctx, path, buf) == 0) *(flag->flag) = !!strtoul(buf, NULL, 0); } decoder->max_available_extent = cxl_decoder_calc_max_available_extent(decoder); break; } } sprintf(path, "%s/target_list", cxldecoder_base); if (sysfs_read_attr(ctx, path, buf) < 0) buf[0] = '\0'; for (i = 0, target_id = strtok_r(buf, ",", &save); target_id; target_id = strtok_r(NULL, ",", &save), i++) { int did = strtoul(target_id, NULL, 0); struct cxl_target *target = calloc(1, sizeof(*target)); if (!target) break; target->id = did; target->position = i; target->decoder = decoder; sprintf(port->dev_buf, "%s/dport%d", port->dev_path, did); target->dev_path = realpath(port->dev_buf, NULL); if (!target->dev_path) { free(target); break; } sprintf(port->dev_buf, "%s/dport%d/physical_node", port->dev_path, did); target->phys_path = realpath(port->dev_buf, NULL); dbg(ctx, "%s: target%ld %s phys_path: %s\n", devname, i, target->dev_path, target->phys_path ? target->phys_path : "none"); sprintf(port->dev_buf, "%s/dport%d/firmware_node", port->dev_path, did); target->fw_path = realpath(port->dev_buf, NULL); dbg(ctx, "%s: target%ld %s fw_path: %s\n", devname, i, target->dev_path, target->fw_path ? target->fw_path : "none"); if (!target->phys_path && target->fw_path) target->phys_path = strdup(target->dev_path); list_add(&decoder->targets, &target->list); } if (target_id) err(ctx, "%s: failed to parse target%ld\n", devpath_to_devname(cxldecoder_base), i); decoder->nr_targets = i; cxl_decoder_foreach(port, decoder_dup) if (decoder_dup->id == decoder->id) { free_decoder(decoder, NULL); free(path); return decoder_dup; } list_add_sorted(&port->decoders, decoder, list, decoder_id_cmp); free(path); return decoder; err_decoder: free(decoder->dev_path); free(decoder->dev_buf); free(decoder); err: free(path); return NULL; } static void cxl_decoders_init(struct cxl_port *port) { struct cxl_ctx *ctx = cxl_port_get_ctx(port); char *decoder_fmt; if (port->decoders_init) return; if (asprintf(&decoder_fmt, "decoder%d.", cxl_port_get_id(port)) < 0) { err(ctx, "%s: failed to add decoder(s)\n", cxl_port_get_devname(port)); return; } port->decoders_init = 1; device_parse(ctx, port->dev_path, decoder_fmt, port, add_cxl_decoder); free(decoder_fmt); } CXL_EXPORT struct cxl_decoder *cxl_decoder_get_first(struct cxl_port *port) { cxl_decoders_init(port); return list_top(&port->decoders, struct cxl_decoder, list); } CXL_EXPORT struct cxl_decoder *cxl_decoder_get_next(struct cxl_decoder *decoder) { struct cxl_port *port = decoder->port; return list_next(&port->decoders, decoder, list); } CXL_EXPORT struct cxl_decoder *cxl_decoder_get_last(struct cxl_port *port) { cxl_decoders_init(port); return list_tail(&port->decoders, struct cxl_decoder, list); } CXL_EXPORT struct cxl_decoder *cxl_decoder_get_prev(struct cxl_decoder *decoder) { struct cxl_port *port = decoder->port; return list_prev(&port->decoders, decoder, list); } CXL_EXPORT struct cxl_ctx *cxl_decoder_get_ctx(struct cxl_decoder *decoder) { return decoder->ctx; } CXL_EXPORT int cxl_decoder_get_id(struct cxl_decoder *decoder) { return decoder->id; } CXL_EXPORT struct cxl_port *cxl_decoder_get_port(struct cxl_decoder *decoder) { return decoder->port; } CXL_EXPORT unsigned long long cxl_decoder_get_resource(struct cxl_decoder *decoder) { return decoder->start; } CXL_EXPORT unsigned long long cxl_decoder_get_size(struct cxl_decoder *decoder) { return decoder->size; } CXL_EXPORT int cxl_root_decoder_get_qos_class(struct cxl_decoder *decoder) { if (!cxl_port_is_root(decoder->port)) return -EINVAL; return decoder->qos_class; } CXL_EXPORT unsigned long long cxl_decoder_get_dpa_resource(struct cxl_decoder *decoder) { struct cxl_port *port = cxl_decoder_get_port(decoder); struct cxl_ctx *ctx = cxl_decoder_get_ctx(decoder); if (!cxl_port_is_endpoint(port)) { err(ctx, "%s: not an endpoint decoder\n", cxl_decoder_get_devname(decoder)); return ULLONG_MAX; } return decoder->dpa_resource; } CXL_EXPORT unsigned long long cxl_decoder_get_dpa_size(struct cxl_decoder *decoder) { struct cxl_port *port = cxl_decoder_get_port(decoder); struct cxl_ctx *ctx = cxl_decoder_get_ctx(decoder); if (!cxl_port_is_endpoint(port)) { err(ctx, "%s: not an endpoint decoder\n", cxl_decoder_get_devname(decoder)); return ULLONG_MAX; } return decoder->dpa_size; } CXL_EXPORT unsigned long long cxl_decoder_get_max_available_extent(struct cxl_decoder *decoder) { return decoder->max_available_extent; } CXL_EXPORT int cxl_decoder_set_dpa_size(struct cxl_decoder *decoder, unsigned long long size) { struct cxl_port *port = cxl_decoder_get_port(decoder); struct cxl_ctx *ctx = cxl_decoder_get_ctx(decoder); char *path = decoder->dev_buf; int len = decoder->buf_len, rc; char buf[SYSFS_ATTR_SIZE]; if (!cxl_port_is_endpoint(port)) { err(ctx, "%s: not an endpoint decoder\n", cxl_decoder_get_devname(decoder)); return -EINVAL; } if (snprintf(path, len, "%s/dpa_size", decoder->dev_path) >= len) { err(ctx, "%s: buffer too small!\n", cxl_decoder_get_devname(decoder)); return -ENOMEM; } sprintf(buf, "%#llx\n", size); rc = sysfs_write_attr(ctx, path, buf); if (rc < 0) return rc; decoder->dpa_size = size; return 0; } CXL_EXPORT int cxl_decoder_set_mode(struct cxl_decoder *decoder, enum cxl_decoder_mode mode) { struct cxl_port *port = cxl_decoder_get_port(decoder); struct cxl_ctx *ctx = cxl_decoder_get_ctx(decoder); char *path = decoder->dev_buf; int len = decoder->buf_len, rc; char buf[SYSFS_ATTR_SIZE]; if (!cxl_port_is_endpoint(port)) { err(ctx, "%s: not an endpoint decoder\n", cxl_decoder_get_devname(decoder)); return -EINVAL; } switch (mode) { case CXL_DECODER_MODE_PMEM: sprintf(buf, "pmem"); break; case CXL_DECODER_MODE_RAM: sprintf(buf, "ram"); break; default: err(ctx, "%s: unsupported mode: %d\n", cxl_decoder_get_devname(decoder), mode); return -EINVAL; } if (snprintf(path, len, "%s/mode", decoder->dev_path) >= len) { err(ctx, "%s: buffer too small!\n", cxl_decoder_get_devname(decoder)); return -ENOMEM; } rc = sysfs_write_attr(ctx, path, buf); if (rc < 0) return rc; decoder->mode = mode; return 0; } CXL_EXPORT enum cxl_decoder_mode cxl_decoder_get_mode(struct cxl_decoder *decoder) { struct cxl_port *port = cxl_decoder_get_port(decoder); struct cxl_ctx *ctx = cxl_decoder_get_ctx(decoder); if (!cxl_port_is_endpoint(port)) { err(ctx, "%s: not an endpoint decoder\n", cxl_decoder_get_devname(decoder)); return CXL_DECODER_MODE_NONE; } return decoder->mode; } CXL_EXPORT enum cxl_decoder_target_type cxl_decoder_get_target_type(struct cxl_decoder *decoder) { return decoder->target_type; } CXL_EXPORT bool cxl_decoder_is_pmem_capable(struct cxl_decoder *decoder) { return decoder->pmem_capable; } CXL_EXPORT bool cxl_decoder_is_volatile_capable(struct cxl_decoder *decoder) { return decoder->volatile_capable; } CXL_EXPORT bool cxl_decoder_is_mem_capable(struct cxl_decoder *decoder) { return decoder->mem_capable; } CXL_EXPORT bool cxl_decoder_is_accelmem_capable(struct cxl_decoder *decoder) { return decoder->accelmem_capable; } CXL_EXPORT bool cxl_decoder_is_locked(struct cxl_decoder *decoder) { return decoder->locked; } CXL_EXPORT unsigned int cxl_decoder_get_interleave_granularity(struct cxl_decoder *decoder) { return decoder->interleave_granularity; } CXL_EXPORT unsigned int cxl_decoder_get_interleave_ways(struct cxl_decoder *decoder) { return decoder->interleave_ways; } CXL_EXPORT struct cxl_region * cxl_decoder_get_region(struct cxl_decoder *decoder) { struct cxl_port *port = cxl_decoder_get_port(decoder); struct cxl_ctx *ctx = cxl_decoder_get_ctx(decoder); char *path = decoder->dev_buf; char buf[SYSFS_ATTR_SIZE]; struct cxl_region *region; struct cxl_decoder *iter; int rc; if (cxl_port_is_root(port)) return NULL; sprintf(path, "%s/region", decoder->dev_path); rc = sysfs_read_attr(ctx, path, buf); if (rc < 0) { err(ctx, "failed to read region name: %s\n", strerror(-rc)); return NULL; } if (strcmp(buf, "") == 0) return NULL; while (!cxl_port_is_root(port)) port = cxl_port_get_parent(port); cxl_decoder_foreach(port, iter) cxl_region_foreach(iter, region) if (strcmp(cxl_region_get_devname(region), buf) == 0) return region; return NULL; } static struct cxl_region *cxl_decoder_create_region(struct cxl_decoder *decoder, enum cxl_decoder_mode mode) { struct cxl_ctx *ctx = cxl_decoder_get_ctx(decoder); char *path = decoder->dev_buf; char buf[SYSFS_ATTR_SIZE]; struct cxl_region *region; int rc; if (mode == CXL_DECODER_MODE_PMEM) sprintf(path, "%s/create_pmem_region", decoder->dev_path); else if (mode == CXL_DECODER_MODE_RAM) sprintf(path, "%s/create_ram_region", decoder->dev_path); rc = sysfs_read_attr(ctx, path, buf); if (rc < 0) { err(ctx, "failed to read new region name: %s\n", strerror(-rc)); return NULL; } rc = sysfs_write_attr(ctx, path, buf); if (rc < 0) { err(ctx, "failed to write new region name: %s\n", strerror(-rc)); return NULL; } /* Force a re-init of regions so that the new one can be discovered */ decoder->regions_init = 0; /* create_region was successful, walk to the new region */ cxl_region_foreach(decoder, region) { const char *devname = cxl_region_get_devname(region); if (strcmp(devname, buf) == 0) goto found; } /* * If walking to the region we just created failed, something has gone * very wrong. Attempt to delete it to avoid leaving a dangling region * id behind. */ err(ctx, "failed to add new region to libcxl\n"); cxl_region_delete_name(decoder, buf); return NULL; found: return region; } CXL_EXPORT struct cxl_region * cxl_decoder_create_pmem_region(struct cxl_decoder *decoder) { return cxl_decoder_create_region(decoder, CXL_DECODER_MODE_PMEM); } CXL_EXPORT struct cxl_region * cxl_decoder_create_ram_region(struct cxl_decoder *decoder) { return cxl_decoder_create_region(decoder, CXL_DECODER_MODE_RAM); } CXL_EXPORT int cxl_decoder_get_nr_targets(struct cxl_decoder *decoder) { return decoder->nr_targets; } CXL_EXPORT const char *cxl_decoder_get_devname(struct cxl_decoder *decoder) { return devpath_to_devname(decoder->dev_path); } CXL_EXPORT struct cxl_memdev * cxl_decoder_get_memdev(struct cxl_decoder *decoder) { struct cxl_port *port = cxl_decoder_get_port(decoder); struct cxl_endpoint *ep; if (!port) return NULL; if (!cxl_port_is_endpoint(port)) return NULL; ep = container_of(port, struct cxl_endpoint, port); if (!ep) return NULL; return cxl_endpoint_get_memdev(ep); } CXL_EXPORT struct cxl_target *cxl_target_get_first(struct cxl_decoder *decoder) { return list_top(&decoder->targets, struct cxl_target, list); } CXL_EXPORT struct cxl_decoder *cxl_target_get_decoder(struct cxl_target *target) { return target->decoder; } CXL_EXPORT struct cxl_target *cxl_target_get_next(struct cxl_target *target) { struct cxl_decoder *decoder = cxl_target_get_decoder(target); return list_next(&decoder->targets, target, list); } CXL_EXPORT const char *cxl_target_get_devname(struct cxl_target *target) { return devpath_to_devname(target->dev_path); } CXL_EXPORT unsigned long cxl_target_get_id(struct cxl_target *target) { return target->id; } CXL_EXPORT int cxl_target_get_position(struct cxl_target *target) { return target->position; } CXL_EXPORT bool cxl_target_maps_memdev(struct cxl_target *target, struct cxl_memdev *memdev) { struct cxl_ctx *ctx = cxl_memdev_get_ctx(memdev); dbg(ctx, "memdev: %s target: %s\n", memdev->host_path, target->dev_path); if (target->phys_path) return !!strstr(memdev->host_path, target->phys_path); return !!strstr(memdev->host_path, target->dev_path); } CXL_EXPORT const char *cxl_target_get_physical_node(struct cxl_target *target) { if (!target->phys_path) return NULL; return devpath_to_devname(target->phys_path); } CXL_EXPORT const char *cxl_target_get_firmware_node(struct cxl_target *target) { if (!target->fw_path) return NULL; return devpath_to_devname(target->fw_path); } CXL_EXPORT struct cxl_target * cxl_decoder_get_target_by_memdev(struct cxl_decoder *decoder, struct cxl_memdev *memdev) { struct cxl_target *target; cxl_target_foreach(decoder, target) if (cxl_target_maps_memdev(target, memdev)) return target; return NULL; } CXL_EXPORT struct cxl_target * cxl_decoder_get_target_by_position(struct cxl_decoder *decoder, int position) { struct cxl_target *target; cxl_target_foreach(decoder, target) if (target->position == position) return target; return NULL; } static void *add_cxl_port(void *parent, int id, const char *cxlport_base) { const char *devname = devpath_to_devname(cxlport_base); struct cxl_port *port, *port_dup; struct cxl_port *parent_port = parent; struct cxl_ctx *ctx = cxl_port_get_ctx(parent_port); int rc; dbg(ctx, "%s: base: \'%s\'\n", devname, cxlport_base); port = calloc(1, sizeof(*port)); if (!port) return NULL; rc = cxl_port_init(port, parent_port, CXL_PORT_SWITCH, ctx, id, cxlport_base); if (rc) goto err; cxl_port_foreach(parent_port, port_dup) if (port_dup->id == port->id) { free_port(port, NULL); return port_dup; } list_add(&parent_port->child_ports, &port->list); return port; err: free(port); return NULL; } static void cxl_ports_init(struct cxl_port *port) { struct cxl_ctx *ctx = cxl_port_get_ctx(port); if (port->ports_init) return; port->ports_init = 1; device_parse(ctx, port->dev_path, "port", port, add_cxl_port); } CXL_EXPORT struct cxl_ctx *cxl_port_get_ctx(struct cxl_port *port) { return port->ctx; } CXL_EXPORT struct cxl_port *cxl_port_get_first(struct cxl_port *port) { cxl_ports_init(port); return list_top(&port->child_ports, struct cxl_port, list); } CXL_EXPORT struct cxl_port *cxl_port_get_next(struct cxl_port *port) { struct cxl_port *parent_port = port->parent; return list_next(&parent_port->child_ports, port, list); } CXL_EXPORT struct cxl_port *cxl_port_get_next_all(struct cxl_port *port, const struct cxl_port *top) { struct cxl_port *child, *iter = port; child = cxl_port_get_first(iter); if (child) return child; while (!cxl_port_get_next(iter) && iter->parent && iter->parent != top) iter = iter->parent; return cxl_port_get_next(iter); } CXL_EXPORT const char *cxl_port_get_devname(struct cxl_port *port) { return devpath_to_devname(port->dev_path); } CXL_EXPORT int cxl_port_get_id(struct cxl_port *port) { return port->id; } CXL_EXPORT struct cxl_port *cxl_port_get_parent(struct cxl_port *port) { return port->parent; } CXL_EXPORT bool cxl_port_is_root(struct cxl_port *port) { return port->type == CXL_PORT_ROOT; } CXL_EXPORT bool cxl_port_is_switch(struct cxl_port *port) { return port->type == CXL_PORT_SWITCH; } CXL_EXPORT bool cxl_port_is_endpoint(struct cxl_port *port) { return port->type == CXL_PORT_ENDPOINT; } CXL_EXPORT int cxl_port_get_depth(struct cxl_port *port) { return port->depth; } CXL_EXPORT struct cxl_bus *cxl_port_get_bus(struct cxl_port *port) { struct cxl_bus *bus; if (!cxl_port_is_enabled(port)) return NULL; if (port->bus) return port->bus; while (port->parent) port = port->parent; bus = container_of(port, typeof(*bus), port); port->bus = bus; return bus; } CXL_EXPORT const char *cxl_port_get_host(struct cxl_port *port) { return devpath_to_devname(port->uport); } CXL_EXPORT struct cxl_dport *cxl_port_get_parent_dport(struct cxl_port *port) { struct cxl_port *parent; struct cxl_dport *dport; const char *name; if (port->parent_dport) return port->parent_dport; if (!port->parent_dport_path) return NULL; parent = cxl_port_get_parent(port); name = devpath_to_devname(port->parent_dport_path); cxl_dport_foreach(parent, dport) if (strcmp(cxl_dport_get_devname(dport), name) == 0) { port->parent_dport = dport; return dport; } return NULL; } CXL_EXPORT bool cxl_port_hosts_memdev(struct cxl_port *port, struct cxl_memdev *memdev) { struct cxl_endpoint *endpoint = cxl_memdev_get_endpoint(memdev); struct cxl_port *iter; if (!endpoint) return false; iter = cxl_endpoint_get_port(endpoint); while (iter && iter != port) iter = iter->parent; return iter != NULL; } CXL_EXPORT int cxl_port_is_enabled(struct cxl_port *port) { struct cxl_ctx *ctx = cxl_port_get_ctx(port); char *path = port->dev_buf; int len = port->buf_len; if (snprintf(path, len, "%s/driver", port->dev_path) >= len) { err(ctx, "%s: buffer too small!\n", cxl_port_get_devname(port)); return 0; } return is_enabled(path); } CXL_EXPORT int cxl_port_disable_invalidate(struct cxl_port *port) { const char *devname = cxl_port_get_devname(port); struct cxl_bus *bus = cxl_port_get_bus(port); struct cxl_ctx *ctx = cxl_port_get_ctx(port); if (cxl_port_is_root(port)) { err(ctx, "%s: can not be disabled through this interface\n", devname); return -EINVAL; } if (!bus) { err(ctx, "%s: failed to invalidate\n", devname); return -ENXIO; } util_unbind(port->dev_path, ctx); if (cxl_port_is_enabled(port)) { err(ctx, "%s: failed to disable\n", devname); return -EBUSY; } dbg(ctx, "%s: disabled\n", devname); bus_invalidate(bus); return 0; } CXL_EXPORT int cxl_port_enable(struct cxl_port *port) { struct cxl_ctx *ctx = cxl_port_get_ctx(port); const char *devname = cxl_port_get_devname(port); if (cxl_port_is_enabled(port)) return 0; util_bind(devname, port->module, "cxl", ctx); if (!cxl_port_is_enabled(port)) { err(ctx, "%s: failed to enable\n", devname); return -ENXIO; } dbg(ctx, "%s: enabled\n", devname); return 0; } CXL_EXPORT struct cxl_bus *cxl_port_to_bus(struct cxl_port *port) { if (!cxl_port_is_root(port)) return NULL; return container_of(port, struct cxl_bus, port); } CXL_EXPORT struct cxl_endpoint *cxl_port_to_endpoint(struct cxl_port *port) { if (!cxl_port_is_endpoint(port)) return NULL; return container_of(port, struct cxl_endpoint, port); } static void *add_cxl_dport(void *parent, int id, const char *cxldport_base) { const char *devname = devpath_to_devname(cxldport_base); struct cxl_dport *dport, *dport_dup; struct cxl_port *port = parent; struct cxl_ctx *ctx = cxl_port_get_ctx(port); dbg(ctx, "%s: base: \'%s\'\n", devname, cxldport_base); dport = calloc(1, sizeof(*dport)); if (!dport) return NULL; dport->id = id; dport->port = port; dport->dev_path = realpath(cxldport_base, NULL); if (!dport->dev_path) goto err; dport->dev_buf = calloc(1, strlen(cxldport_base) + 50); if (!dport->dev_buf) goto err; dport->buf_len = strlen(cxldport_base) + 50; sprintf(dport->dev_buf, "%s/physical_node", cxldport_base); dport->phys_path = realpath(dport->dev_buf, NULL); sprintf(dport->dev_buf, "%s/firmware_node", cxldport_base); dport->fw_path = realpath(dport->dev_buf, NULL); if (!dport->phys_path && dport->fw_path) dport->phys_path = strdup(dport->dev_path); cxl_dport_foreach(port, dport_dup) if (dport_dup->id == dport->id) { free_dport(dport, NULL); return dport_dup; } port->nr_dports++; list_add(&port->dports, &dport->list); return dport; err: free_dport(dport, NULL); return NULL; } static void cxl_dports_init(struct cxl_port *port) { struct cxl_ctx *ctx = cxl_port_get_ctx(port); if (port->dports_init) return; port->dports_init = 1; device_parse(ctx, port->dev_path, "dport", port, add_cxl_dport); } CXL_EXPORT int cxl_port_get_nr_dports(struct cxl_port *port) { if (!port->dports_init) cxl_dports_init(port); return port->nr_dports; } CXL_EXPORT struct cxl_dport *cxl_dport_get_first(struct cxl_port *port) { cxl_dports_init(port); return list_top(&port->dports, struct cxl_dport, list); } CXL_EXPORT struct cxl_dport *cxl_dport_get_next(struct cxl_dport *dport) { struct cxl_port *port = dport->port; return list_next(&port->dports, dport, list); } CXL_EXPORT const char *cxl_dport_get_devname(struct cxl_dport *dport) { return devpath_to_devname(dport->dev_path); } CXL_EXPORT const char *cxl_dport_get_physical_node(struct cxl_dport *dport) { if (!dport->phys_path) return NULL; return devpath_to_devname(dport->phys_path); } CXL_EXPORT const char *cxl_dport_get_firmware_node(struct cxl_dport *dport) { if (!dport->fw_path) return NULL; return devpath_to_devname(dport->fw_path); } CXL_EXPORT int cxl_dport_get_id(struct cxl_dport *dport) { return dport->id; } CXL_EXPORT struct cxl_port *cxl_dport_get_port(struct cxl_dport *dport) { return dport->port; } CXL_EXPORT bool cxl_dport_maps_memdev(struct cxl_dport *dport, struct cxl_memdev *memdev) { struct cxl_ctx *ctx = cxl_memdev_get_ctx(memdev); dbg(ctx, "memdev: %s dport: %s\n", memdev->host_path, dport->dev_path); if (dport->phys_path) return !!strstr(memdev->host_path, dport->phys_path); return !!strstr(memdev->host_path, dport->dev_path); } CXL_EXPORT struct cxl_dport * cxl_port_get_dport_by_memdev(struct cxl_port *port, struct cxl_memdev *memdev) { struct cxl_dport *dport; cxl_dport_foreach(port, dport) if (cxl_dport_maps_memdev(dport, memdev)) return dport; return NULL; } CXL_EXPORT int cxl_port_decoders_committed(struct cxl_port *port) { return port->decoders_committed; } static void *add_cxl_bus(void *parent, int id, const char *cxlbus_base) { const char *devname = devpath_to_devname(cxlbus_base); struct cxl_bus *bus, *bus_dup; struct cxl_ctx *ctx = parent; struct cxl_port *port; int rc; dbg(ctx, "%s: base: \'%s\'\n", devname, cxlbus_base); bus = calloc(1, sizeof(*bus)); if (!bus) return NULL; port = &bus->port; rc = cxl_port_init(port, NULL, CXL_PORT_ROOT, ctx, id, cxlbus_base); if (rc) goto err; cxl_bus_foreach(ctx, bus_dup) if (bus_dup->port.id == bus->port.id) { free_bus(bus, NULL); return bus_dup; } list_add(&ctx->buses, &port->list); return bus; err: free(bus); return NULL; } static void cxl_buses_init(struct cxl_ctx *ctx) { if (ctx->buses_init) return; ctx->buses_init = 1; device_parse(ctx, "/sys/bus/cxl/devices", "root", ctx, add_cxl_bus); } CXL_EXPORT struct cxl_bus *cxl_bus_get_first(struct cxl_ctx *ctx) { cxl_buses_init(ctx); return list_top(&ctx->buses, struct cxl_bus, port.list); } CXL_EXPORT struct cxl_bus *cxl_bus_get_next(struct cxl_bus *bus) { struct cxl_ctx *ctx = bus->port.ctx; return list_next(&ctx->buses, bus, port.list); } CXL_EXPORT const char *cxl_bus_get_devname(struct cxl_bus *bus) { struct cxl_port *port = &bus->port; return devpath_to_devname(port->dev_path); } CXL_EXPORT int cxl_bus_get_id(struct cxl_bus *bus) { struct cxl_port *port = &bus->port; return port->id; } CXL_EXPORT struct cxl_port *cxl_bus_get_port(struct cxl_bus *bus) { return &bus->port; } CXL_EXPORT const char *cxl_bus_get_provider(struct cxl_bus *bus) { struct cxl_port *port = &bus->port; const char *devname = devpath_to_devname(port->uport); if (strcmp(devname, "ACPI0017:00") == 0) return "ACPI.CXL"; if (strcmp(devname, "cxl_acpi.0") == 0) return "cxl_test"; return devname; } CXL_EXPORT struct cxl_ctx *cxl_bus_get_ctx(struct cxl_bus *bus) { return cxl_port_get_ctx(&bus->port); } CXL_EXPORT void cxl_cmd_unref(struct cxl_cmd *cmd) { if (!cmd) return; if (--cmd->refcount == 0) { free(cmd->query_cmd); free(cmd->send_cmd); free(cmd->input_payload); free(cmd->output_payload); free(cmd); } } CXL_EXPORT void cxl_cmd_ref(struct cxl_cmd *cmd) { cmd->refcount++; } static int cxl_cmd_alloc_query(struct cxl_cmd *cmd, int num_cmds) { size_t size; if (!cmd) return -EINVAL; if (cmd->query_cmd != NULL) free(cmd->query_cmd); size = struct_size(cmd->query_cmd, commands, num_cmds); if (size == SIZE_MAX) return -EOVERFLOW; cmd->query_cmd = calloc(1, size); if (!cmd->query_cmd) return -ENOMEM; cmd->query_cmd->n_commands = num_cmds; return 0; } static struct cxl_cmd *cxl_cmd_new(struct cxl_memdev *memdev) { struct cxl_cmd *cmd; size_t size; size = sizeof(*cmd); cmd = calloc(1, size); if (!cmd) return NULL; cxl_cmd_ref(cmd); cmd->memdev = memdev; return cmd; } static int __do_cmd(struct cxl_cmd *cmd, int ioctl_cmd, int fd) { void *cmd_buf; int rc; switch (ioctl_cmd) { case CXL_MEM_QUERY_COMMANDS: cmd_buf = cmd->query_cmd; break; case CXL_MEM_SEND_COMMAND: cmd_buf = cmd->send_cmd; break; default: return -EINVAL; } rc = ioctl(fd, ioctl_cmd, cmd_buf); if (rc < 0) rc = -errno; return rc; } static int do_cmd(struct cxl_cmd *cmd, int ioctl_cmd) { char *path; struct stat st; unsigned int major, minor; int rc = 0, fd; struct cxl_memdev *memdev = cmd->memdev; struct cxl_ctx *ctx = cxl_memdev_get_ctx(memdev); const char *devname = cxl_memdev_get_devname(memdev); major = cxl_memdev_get_major(memdev); minor = cxl_memdev_get_minor(memdev); if (asprintf(&path, "/dev/cxl/%s", devname) < 0) return -ENOMEM; fd = open(path, O_RDWR); if (fd < 0) { err(ctx, "failed to open %s: %s\n", path, strerror(errno)); rc = -errno; goto out; } if (fstat(fd, &st) >= 0 && S_ISCHR(st.st_mode) && major(st.st_rdev) == major && minor(st.st_rdev) == minor) { rc = __do_cmd(cmd, ioctl_cmd, fd); } else { err(ctx, "failed to validate %s as a CXL memdev node\n", path); rc = -ENXIO; } close(fd); out: free(path); return rc; } static int alloc_do_query(struct cxl_cmd *cmd, int num_cmds) { struct cxl_ctx *ctx = cxl_memdev_get_ctx(cmd->memdev); int rc; rc = cxl_cmd_alloc_query(cmd, num_cmds); if (rc) return rc; rc = do_cmd(cmd, CXL_MEM_QUERY_COMMANDS); if (rc < 0) err(ctx, "%s: query commands failed: %s\n", cxl_memdev_get_devname(cmd->memdev), strerror(-rc)); return rc; } static int cxl_cmd_do_query(struct cxl_cmd *cmd) { struct cxl_memdev *memdev = cmd->memdev; struct cxl_ctx *ctx = cxl_memdev_get_ctx(memdev); const char *devname = cxl_memdev_get_devname(memdev); int rc, n_commands; switch (cmd->query_status) { case CXL_CMD_QUERY_OK: return 0; case CXL_CMD_QUERY_UNSUPPORTED: return -EOPNOTSUPP; case CXL_CMD_QUERY_NOT_RUN: break; default: err(ctx, "%s: Unknown query_status %d\n", devname, cmd->query_status); return -EINVAL; } rc = alloc_do_query(cmd, 0); if (rc) return rc; n_commands = cmd->query_cmd->n_commands; dbg(ctx, "%s: supports %d commands\n", devname, n_commands); return alloc_do_query(cmd, n_commands); } static int cxl_cmd_validate(struct cxl_cmd *cmd, u32 cmd_id) { struct cxl_memdev *memdev = cmd->memdev; struct cxl_mem_query_commands *query = cmd->query_cmd; const char *devname = cxl_memdev_get_devname(memdev); struct cxl_ctx *ctx = cxl_memdev_get_ctx(memdev); u32 i; for (i = 0; i < query->n_commands; i++) { struct cxl_command_info *cinfo = &query->commands[i]; const char *cmd_name = cxl_command_names[cinfo->id].name; if (cinfo->id != cmd_id) continue; dbg(ctx, "%s: %s: in: %d, out %d, flags: %#08x\n", devname, cmd_name, cinfo->size_in, cinfo->size_out, cinfo->flags); cmd->query_idx = i; cmd->query_status = CXL_CMD_QUERY_OK; return 0; } cmd->query_status = CXL_CMD_QUERY_UNSUPPORTED; return -EOPNOTSUPP; } CXL_EXPORT int cxl_cmd_set_input_payload(struct cxl_cmd *cmd, void *buf, int size) { struct cxl_memdev *memdev = cmd->memdev; if (size > memdev->payload_max || size < 0) return -EINVAL; if (!buf) { /* If the user didn't supply a buffer, allocate it */ cmd->input_payload = calloc(1, size); if (!cmd->input_payload) return -ENOMEM; cmd->send_cmd->in.payload = (u64)cmd->input_payload; } else { /* * Use user-buffer as is. If an automatic allocation was * previously made (based on a fixed size from query), * it will get freed during unref. */ cmd->send_cmd->in.payload = (u64)buf; } cmd->send_cmd->in.size = size; return 0; } CXL_EXPORT int cxl_cmd_set_output_payload(struct cxl_cmd *cmd, void *buf, int size) { struct cxl_memdev *memdev = cmd->memdev; if (size > memdev->payload_max || size < 0) return -EINVAL; if (!buf) { /* If the user didn't supply a buffer, allocate it */ cmd->output_payload = calloc(1, size); if (!cmd->output_payload) return -ENOMEM; cmd->send_cmd->out.payload = (u64)cmd->output_payload; } else { /* * Use user-buffer as is. If an automatic allocation was * previously made (based on a fixed size from query), * it will get freed during unref. */ cmd->send_cmd->out.payload = (u64)buf; } cmd->send_cmd->out.size = size; return 0; } static int cxl_cmd_alloc_send(struct cxl_cmd *cmd, u32 cmd_id) { struct cxl_mem_query_commands *query = cmd->query_cmd; struct cxl_command_info *cinfo = &query->commands[cmd->query_idx]; size_t size; size = sizeof(struct cxl_send_command); cmd->send_cmd = calloc(1, size); if (!cmd->send_cmd) return -ENOMEM; if (cinfo->id != cmd_id) return -EINVAL; cmd->send_cmd->id = cmd_id; if (cinfo->size_in > 0) { cmd->input_payload = calloc(1, cinfo->size_in); if (!cmd->input_payload) return -ENOMEM; cmd->send_cmd->in.payload = (u64)cmd->input_payload; cmd->send_cmd->in.size = cinfo->size_in; } if (cinfo->size_out > 0) { cmd->output_payload = calloc(1, cinfo->size_out); if (!cmd->output_payload) return -ENOMEM; cmd->send_cmd->out.payload = (u64)cmd->output_payload; cmd->send_cmd->out.size = cinfo->size_out; } return 0; } static struct cxl_cmd *cxl_cmd_new_generic(struct cxl_memdev *memdev, u32 cmd_id) { const char *devname = cxl_memdev_get_devname(memdev); struct cxl_ctx *ctx = cxl_memdev_get_ctx(memdev); struct cxl_cmd *cmd; int rc; cmd = cxl_cmd_new(memdev); if (!cmd) return NULL; rc = cxl_cmd_do_query(cmd); if (rc) { err(ctx, "%s: query returned: %s\n", devname, strerror(-rc)); goto fail; } rc = cxl_cmd_validate(cmd, cmd_id); if (rc) { errno = -rc; goto fail; } rc = cxl_cmd_alloc_send(cmd, cmd_id); if (rc) { errno = -rc; goto fail; } cmd->status = 1; return cmd; fail: cxl_cmd_unref(cmd); return NULL; } CXL_EXPORT const char *cxl_cmd_get_devname(struct cxl_cmd *cmd) { return cxl_memdev_get_devname(cmd->memdev); } static int cxl_cmd_validate_status(struct cxl_cmd *cmd, u32 id) { if (cmd->send_cmd->id != id) return -EINVAL; if (cmd->status < 0) return cmd->status; return 0; } static uint64_t cxl_capacity_to_bytes(leint64_t size) { return le64_to_cpu(size) * CXL_CAPACITY_MULTIPLIER; } /* Helpers for health_info fields (no endian conversion) */ #define cmd_get_field_u8(cmd, n, N, field) \ do { \ struct cxl_cmd_##n *c = \ (struct cxl_cmd_##n *)cmd->send_cmd->out.payload; \ int rc = cxl_cmd_validate_status(cmd, CXL_MEM_COMMAND_ID_##N); \ if (rc) \ return rc; \ return c->field; \ } while(0) #define cmd_get_field_u16(cmd, n, N, field) \ do { \ struct cxl_cmd_##n *c = \ (struct cxl_cmd_##n *)cmd->send_cmd->out.payload; \ int rc = cxl_cmd_validate_status(cmd, CXL_MEM_COMMAND_ID_##N); \ if (rc) \ return rc; \ return le16_to_cpu(c->field); \ } while(0) #define cmd_get_field_u32(cmd, n, N, field) \ do { \ struct cxl_cmd_##n *c = \ (struct cxl_cmd_##n *)cmd->send_cmd->out.payload; \ int rc = cxl_cmd_validate_status(cmd, CXL_MEM_COMMAND_ID_##N); \ if (rc) \ return rc; \ return le32_to_cpu(c->field); \ } while(0) #define cmd_get_field_u8_mask(cmd, n, N, field, mask) \ do { \ struct cxl_cmd_##n *c = \ (struct cxl_cmd_##n *)cmd->send_cmd->out.payload; \ int rc = cxl_cmd_validate_status(cmd, CXL_MEM_COMMAND_ID_##N); \ if (rc) \ return rc; \ return !!(c->field & mask); \ } while(0) CXL_EXPORT struct cxl_cmd * cxl_cmd_new_get_alert_config(struct cxl_memdev *memdev) { return cxl_cmd_new_generic(memdev, CXL_MEM_COMMAND_ID_GET_ALERT_CONFIG); } #define cmd_alert_get_valid_alerts_field(c, m) \ cmd_get_field_u8_mask(c, get_alert_config, GET_ALERT_CONFIG, \ valid_alerts, m) CXL_EXPORT int cxl_cmd_alert_config_life_used_prog_warn_threshold_valid(struct cxl_cmd *cmd) { cmd_alert_get_valid_alerts_field( cmd, CXL_CMD_ALERT_CONFIG_VALID_ALERTS_LIFE_USED_PROG_WARN_THRESHOLD_MASK); } CXL_EXPORT int cxl_cmd_alert_config_dev_over_temperature_prog_warn_threshold_valid( struct cxl_cmd *cmd) { cmd_alert_get_valid_alerts_field( cmd, CXL_CMD_ALERT_CONFIG_VALID_ALERTS_DEV_OVER_TEMPERATURE_PROG_WARN_THRESHOLD_MASK); } CXL_EXPORT int cxl_cmd_alert_config_dev_under_temperature_prog_warn_threshold_valid( struct cxl_cmd *cmd) { cmd_alert_get_valid_alerts_field( cmd, CXL_CMD_ALERT_CONFIG_VALID_ALERTS_DEV_UNDER_TEMPERATURE_PROG_WARN_THRESHOLD_MASK); } CXL_EXPORT int cxl_cmd_alert_config_corrected_volatile_mem_err_prog_warn_threshold_valid( struct cxl_cmd *cmd) { cmd_alert_get_valid_alerts_field( cmd, CXL_CMD_ALERT_CONFIG_VALID_ALERTS_CORRECTED_VOLATILE_MEM_ERR_PROG_WARN_THRESHOLD_MASK); } CXL_EXPORT int cxl_cmd_alert_config_corrected_pmem_err_prog_warn_threshold_valid( struct cxl_cmd *cmd) { cmd_alert_get_valid_alerts_field( cmd, CXL_CMD_ALERT_CONFIG_VALID_ALERTS_CORRECTED_PMEM_ERR_PROG_WARN_THRESHOLD_MASK); } #define cmd_alert_get_prog_alerts_field(c, m) \ cmd_get_field_u8_mask(c, get_alert_config, GET_ALERT_CONFIG, \ programmable_alerts, m) CXL_EXPORT int cxl_cmd_alert_config_life_used_prog_warn_threshold_writable(struct cxl_cmd *cmd) { cmd_alert_get_prog_alerts_field( cmd, CXL_CMD_ALERT_CONFIG_PROG_ALERTS_LIFE_USED_PROG_WARN_THRESHOLD_MASK); } CXL_EXPORT int cxl_cmd_alert_config_dev_over_temperature_prog_warn_threshold_writable( struct cxl_cmd *cmd) { cmd_alert_get_prog_alerts_field( cmd, CXL_CMD_ALERT_CONFIG_PROG_ALERTS_DEV_OVER_TEMPERATURE_PROG_WARN_THRESHOLD_MASK); } CXL_EXPORT int cxl_cmd_alert_config_dev_under_temperature_prog_warn_threshold_writable( struct cxl_cmd *cmd) { cmd_alert_get_prog_alerts_field( cmd, CXL_CMD_ALERT_CONFIG_PROG_ALERTS_DEV_UNDER_TEMPERATURE_PROG_WARN_THRESHOLD_MASK); } CXL_EXPORT int cxl_cmd_alert_config_corrected_volatile_mem_err_prog_warn_threshold_writable( struct cxl_cmd *cmd) { cmd_alert_get_prog_alerts_field( cmd, CXL_CMD_ALERT_CONFIG_PROG_ALERTS_CORRECTED_VOLATILE_MEM_ERR_PROG_WARN_THRESHOLD_MASK); } CXL_EXPORT int cxl_cmd_alert_config_corrected_pmem_err_prog_warn_threshold_writable( struct cxl_cmd *cmd) { cmd_alert_get_prog_alerts_field( cmd, CXL_CMD_ALERT_CONFIG_PROG_ALERTS_CORRECTED_PMEM_ERR_PROG_WARN_THRESHOLD_MASK); } CXL_EXPORT int cxl_cmd_alert_config_get_life_used_crit_alert_threshold(struct cxl_cmd *cmd) { cmd_get_field_u8(cmd, get_alert_config, GET_ALERT_CONFIG, life_used_crit_alert_threshold); } CXL_EXPORT int cxl_cmd_alert_config_get_life_used_prog_warn_threshold(struct cxl_cmd *cmd) { cmd_get_field_u8(cmd, get_alert_config, GET_ALERT_CONFIG, life_used_prog_warn_threshold); } CXL_EXPORT int cxl_cmd_alert_config_get_dev_over_temperature_crit_alert_threshold( struct cxl_cmd *cmd) { cmd_get_field_u16(cmd, get_alert_config, GET_ALERT_CONFIG, dev_over_temperature_crit_alert_threshold); } CXL_EXPORT int cxl_cmd_alert_config_get_dev_under_temperature_crit_alert_threshold( struct cxl_cmd *cmd) { cmd_get_field_u16(cmd, get_alert_config, GET_ALERT_CONFIG, dev_under_temperature_crit_alert_threshold); } CXL_EXPORT int cxl_cmd_alert_config_get_dev_over_temperature_prog_warn_threshold( struct cxl_cmd *cmd) { cmd_get_field_u16(cmd, get_alert_config, GET_ALERT_CONFIG, dev_over_temperature_prog_warn_threshold); } CXL_EXPORT int cxl_cmd_alert_config_get_dev_under_temperature_prog_warn_threshold( struct cxl_cmd *cmd) { cmd_get_field_u16(cmd, get_alert_config, GET_ALERT_CONFIG, dev_under_temperature_prog_warn_threshold); } CXL_EXPORT int cxl_cmd_alert_config_get_corrected_volatile_mem_err_prog_warn_threshold( struct cxl_cmd *cmd) { cmd_get_field_u16(cmd, get_alert_config, GET_ALERT_CONFIG, corrected_volatile_mem_err_prog_warn_threshold); } CXL_EXPORT int cxl_cmd_alert_config_get_corrected_pmem_err_prog_warn_threshold( struct cxl_cmd *cmd) { cmd_get_field_u16(cmd, get_alert_config, GET_ALERT_CONFIG, corrected_pmem_err_prog_warn_threshold); } CXL_EXPORT struct cxl_cmd *cxl_cmd_new_get_health_info( struct cxl_memdev *memdev) { return cxl_cmd_new_generic(memdev, CXL_MEM_COMMAND_ID_GET_HEALTH_INFO); } #define cmd_health_get_status_field(c, m) \ cmd_get_field_u8_mask(c, get_health_info, GET_HEALTH_INFO, health_status, m) CXL_EXPORT int cxl_cmd_health_info_get_maintenance_needed(struct cxl_cmd *cmd) { cmd_health_get_status_field(cmd, CXL_CMD_HEALTH_INFO_STATUS_MAINTENANCE_NEEDED_MASK); } CXL_EXPORT int cxl_cmd_health_info_get_performance_degraded(struct cxl_cmd *cmd) { cmd_health_get_status_field(cmd, CXL_CMD_HEALTH_INFO_STATUS_PERFORMANCE_DEGRADED_MASK); } CXL_EXPORT int cxl_cmd_health_info_get_hw_replacement_needed(struct cxl_cmd *cmd) { cmd_health_get_status_field(cmd, CXL_CMD_HEALTH_INFO_STATUS_HW_REPLACEMENT_NEEDED_MASK); } #define cmd_health_check_media_field(cmd, f) \ do { \ struct cxl_cmd_get_health_info *c = \ (struct cxl_cmd_get_health_info *)cmd->send_cmd->out.payload; \ int rc = cxl_cmd_validate_status(cmd, \ CXL_MEM_COMMAND_ID_GET_HEALTH_INFO); \ if (rc) \ return rc; \ return (c->media_status == f); \ } while(0) CXL_EXPORT int cxl_cmd_health_info_get_media_normal(struct cxl_cmd *cmd) { cmd_health_check_media_field(cmd, CXL_CMD_HEALTH_INFO_MEDIA_STATUS_NORMAL); } CXL_EXPORT int cxl_cmd_health_info_get_media_not_ready(struct cxl_cmd *cmd) { cmd_health_check_media_field(cmd, CXL_CMD_HEALTH_INFO_MEDIA_STATUS_NOT_READY); } CXL_EXPORT int cxl_cmd_health_info_get_media_persistence_lost(struct cxl_cmd *cmd) { cmd_health_check_media_field(cmd, CXL_CMD_HEALTH_INFO_MEDIA_STATUS_PERSISTENCE_LOST); } CXL_EXPORT int cxl_cmd_health_info_get_media_data_lost(struct cxl_cmd *cmd) { cmd_health_check_media_field(cmd, CXL_CMD_HEALTH_INFO_MEDIA_STATUS_DATA_LOST); } CXL_EXPORT int cxl_cmd_health_info_get_media_powerloss_persistence_loss(struct cxl_cmd *cmd) { cmd_health_check_media_field(cmd, CXL_CMD_HEALTH_INFO_MEDIA_STATUS_POWERLOSS_PERSISTENCE_LOSS); } CXL_EXPORT int cxl_cmd_health_info_get_media_shutdown_persistence_loss(struct cxl_cmd *cmd) { cmd_health_check_media_field(cmd, CXL_CMD_HEALTH_INFO_MEDIA_STATUS_SHUTDOWN_PERSISTENCE_LOSS); } CXL_EXPORT int cxl_cmd_health_info_get_media_persistence_loss_imminent(struct cxl_cmd *cmd) { cmd_health_check_media_field(cmd, CXL_CMD_HEALTH_INFO_MEDIA_STATUS_PERSISTENCE_LOSS_IMMINENT); } CXL_EXPORT int cxl_cmd_health_info_get_media_powerloss_data_loss(struct cxl_cmd *cmd) { cmd_health_check_media_field(cmd, CXL_CMD_HEALTH_INFO_MEDIA_STATUS_POWERLOSS_DATA_LOSS); } CXL_EXPORT int cxl_cmd_health_info_get_media_shutdown_data_loss(struct cxl_cmd *cmd) { cmd_health_check_media_field(cmd, CXL_CMD_HEALTH_INFO_MEDIA_STATUS_SHUTDOWN_DATA_LOSS); } CXL_EXPORT int cxl_cmd_health_info_get_media_data_loss_imminent(struct cxl_cmd *cmd) { cmd_health_check_media_field(cmd, CXL_CMD_HEALTH_INFO_MEDIA_STATUS_DATA_LOSS_IMMINENT); } #define cmd_health_check_ext_field(cmd, fname, type) \ do { \ struct cxl_cmd_get_health_info *c = \ (struct cxl_cmd_get_health_info *)cmd->send_cmd->out.payload; \ int rc = cxl_cmd_validate_status(cmd, \ CXL_MEM_COMMAND_ID_GET_HEALTH_INFO); \ if (rc) \ return rc; \ return (FIELD_GET(fname##_MASK, c->ext_status) == \ fname##_##type); \ } while(0) CXL_EXPORT int cxl_cmd_health_info_get_ext_life_used_normal(struct cxl_cmd *cmd) { cmd_health_check_ext_field(cmd, CXL_CMD_HEALTH_INFO_EXT_LIFE_USED, NORMAL); } CXL_EXPORT int cxl_cmd_health_info_get_ext_life_used_warning(struct cxl_cmd *cmd) { cmd_health_check_ext_field(cmd, CXL_CMD_HEALTH_INFO_EXT_LIFE_USED, WARNING); } CXL_EXPORT int cxl_cmd_health_info_get_ext_life_used_critical(struct cxl_cmd *cmd) { cmd_health_check_ext_field(cmd, CXL_CMD_HEALTH_INFO_EXT_LIFE_USED, CRITICAL); } CXL_EXPORT int cxl_cmd_health_info_get_ext_temperature_normal(struct cxl_cmd *cmd) { cmd_health_check_ext_field(cmd, CXL_CMD_HEALTH_INFO_EXT_TEMPERATURE, NORMAL); } CXL_EXPORT int cxl_cmd_health_info_get_ext_temperature_warning(struct cxl_cmd *cmd) { cmd_health_check_ext_field(cmd, CXL_CMD_HEALTH_INFO_EXT_TEMPERATURE, WARNING); } CXL_EXPORT int cxl_cmd_health_info_get_ext_temperature_critical(struct cxl_cmd *cmd) { cmd_health_check_ext_field(cmd, CXL_CMD_HEALTH_INFO_EXT_TEMPERATURE, CRITICAL); } CXL_EXPORT int cxl_cmd_health_info_get_ext_corrected_volatile_normal(struct cxl_cmd *cmd) { cmd_health_check_ext_field(cmd, CXL_CMD_HEALTH_INFO_EXT_CORRECTED_VOLATILE, NORMAL); } CXL_EXPORT int cxl_cmd_health_info_get_ext_corrected_volatile_warning(struct cxl_cmd *cmd) { cmd_health_check_ext_field(cmd, CXL_CMD_HEALTH_INFO_EXT_CORRECTED_VOLATILE, WARNING); } CXL_EXPORT int cxl_cmd_health_info_get_ext_corrected_persistent_normal(struct cxl_cmd *cmd) { cmd_health_check_ext_field(cmd, CXL_CMD_HEALTH_INFO_EXT_CORRECTED_PERSISTENT, NORMAL); } CXL_EXPORT int cxl_cmd_health_info_get_ext_corrected_persistent_warning(struct cxl_cmd *cmd) { cmd_health_check_ext_field(cmd, CXL_CMD_HEALTH_INFO_EXT_CORRECTED_PERSISTENT, WARNING); } static int health_info_get_life_used_raw(struct cxl_cmd *cmd) { cmd_get_field_u8(cmd, get_health_info, GET_HEALTH_INFO, life_used); } CXL_EXPORT int cxl_cmd_health_info_get_life_used(struct cxl_cmd *cmd) { int rc = health_info_get_life_used_raw(cmd); if (rc < 0) return rc; if (rc == CXL_CMD_HEALTH_INFO_LIFE_USED_NOT_IMPL) return -EOPNOTSUPP; return rc; } static int health_info_get_temperature_raw(struct cxl_cmd *cmd) { cmd_get_field_u16(cmd, get_health_info, GET_HEALTH_INFO, temperature); } CXL_EXPORT int cxl_cmd_health_info_get_temperature(struct cxl_cmd *cmd) { int rc = health_info_get_temperature_raw(cmd); if (rc < 0) return rc; if (rc == CXL_CMD_HEALTH_INFO_TEMPERATURE_NOT_IMPL) return -EOPNOTSUPP; return rc; } CXL_EXPORT int cxl_cmd_health_info_get_dirty_shutdowns(struct cxl_cmd *cmd) { cmd_get_field_u32(cmd, get_health_info, GET_HEALTH_INFO, dirty_shutdowns); } CXL_EXPORT int cxl_cmd_health_info_get_volatile_errors(struct cxl_cmd *cmd) { cmd_get_field_u32(cmd, get_health_info, GET_HEALTH_INFO, volatile_errors); } CXL_EXPORT int cxl_cmd_health_info_get_pmem_errors(struct cxl_cmd *cmd) { cmd_get_field_u32(cmd, get_health_info, GET_HEALTH_INFO, pmem_errors); } CXL_EXPORT struct cxl_cmd *cxl_cmd_new_identify(struct cxl_memdev *memdev) { return cxl_cmd_new_generic(memdev, CXL_MEM_COMMAND_ID_IDENTIFY); } static struct cxl_cmd_identify * cmd_to_identify(struct cxl_cmd *cmd) { if (cxl_cmd_validate_status(cmd, CXL_MEM_COMMAND_ID_IDENTIFY)) return NULL; return cmd->output_payload; } CXL_EXPORT int cxl_cmd_identify_get_fw_rev(struct cxl_cmd *cmd, char *fw_rev, int fw_len) { struct cxl_cmd_identify *id = (struct cxl_cmd_identify *)cmd->send_cmd->out.payload; if (cmd->send_cmd->id != CXL_MEM_COMMAND_ID_IDENTIFY) return -EINVAL; if (cmd->status < 0) return cmd->status; if (fw_len > 0) memcpy(fw_rev, id->fw_revision, min(fw_len, CXL_CMD_IDENTIFY_FW_REV_LENGTH)); return 0; } CXL_EXPORT unsigned long long cxl_cmd_identify_get_partition_align( struct cxl_cmd *cmd) { struct cxl_cmd_identify *c; c = cmd_to_identify(cmd); if (!c) return ULLONG_MAX; return cxl_capacity_to_bytes(c->partition_align); } CXL_EXPORT unsigned int cxl_cmd_identify_get_label_size(struct cxl_cmd *cmd) { struct cxl_cmd_identify *id = (struct cxl_cmd_identify *)cmd->send_cmd->out.payload; if (cmd->send_cmd->id != CXL_MEM_COMMAND_ID_IDENTIFY) return -EINVAL; if (cmd->status < 0) return cmd->status; return le32_to_cpu(id->lsa_size); } CXL_EXPORT unsigned long long cxl_cmd_identify_get_total_size(struct cxl_cmd *cmd) { struct cxl_cmd_identify *c; c = cmd_to_identify(cmd); if (!c) return ULLONG_MAX; return cxl_capacity_to_bytes(c->total_capacity); } CXL_EXPORT unsigned long long cxl_cmd_identify_get_volatile_only_size(struct cxl_cmd *cmd) { struct cxl_cmd_identify *c; c = cmd_to_identify(cmd); if (!c) return ULLONG_MAX; return cxl_capacity_to_bytes(c->volatile_capacity); } CXL_EXPORT unsigned long long cxl_cmd_identify_get_persistent_only_size(struct cxl_cmd *cmd) { struct cxl_cmd_identify *c; c = cmd_to_identify(cmd); if (!c) return ULLONG_MAX; return cxl_capacity_to_bytes(c->persistent_capacity); } CXL_EXPORT struct cxl_cmd *cxl_cmd_new_raw(struct cxl_memdev *memdev, int opcode) { struct cxl_cmd *cmd; /* opcode '0' is reserved */ if (opcode <= 0) { errno = EINVAL; return NULL; } cmd = cxl_cmd_new_generic(memdev, CXL_MEM_COMMAND_ID_RAW); if (!cmd) return NULL; cmd->send_cmd->raw.opcode = opcode; return cmd; } CXL_EXPORT struct cxl_cmd *cxl_cmd_new_read_label(struct cxl_memdev *memdev, unsigned int offset, unsigned int length) { struct cxl_cmd_get_lsa_in *get_lsa; struct cxl_cmd *cmd; cmd = cxl_cmd_new_generic(memdev, CXL_MEM_COMMAND_ID_GET_LSA); if (!cmd) return NULL; get_lsa = (struct cxl_cmd_get_lsa_in *)cmd->send_cmd->in.payload; get_lsa->offset = cpu_to_le32(offset); get_lsa->length = cpu_to_le32(length); return cmd; } CXL_EXPORT ssize_t cxl_cmd_read_label_get_payload(struct cxl_cmd *cmd, void *buf, unsigned int length) { struct cxl_cmd_get_lsa_in *get_lsa; void *payload; int rc; rc = cxl_cmd_validate_status(cmd, CXL_MEM_COMMAND_ID_GET_LSA); if (rc) return rc; get_lsa = (struct cxl_cmd_get_lsa_in *)cmd->send_cmd->in.payload; if (length > le32_to_cpu(get_lsa->length)) return -EINVAL; payload = (void *)cmd->send_cmd->out.payload; memcpy(buf, payload, length); return length; } CXL_EXPORT struct cxl_cmd *cxl_cmd_new_get_partition(struct cxl_memdev *memdev) { return cxl_cmd_new_generic(memdev, CXL_MEM_COMMAND_ID_GET_PARTITION_INFO); } static struct cxl_cmd_get_partition * cmd_to_get_partition(struct cxl_cmd *cmd) { if (cxl_cmd_validate_status(cmd, CXL_MEM_COMMAND_ID_GET_PARTITION_INFO)) return NULL; return cmd->output_payload; } CXL_EXPORT unsigned long long cxl_cmd_partition_get_active_volatile_size(struct cxl_cmd *cmd) { struct cxl_cmd_get_partition *c; c = cmd_to_get_partition(cmd); if (!c) return ULLONG_MAX; return cxl_capacity_to_bytes(c->active_volatile); } CXL_EXPORT unsigned long long cxl_cmd_partition_get_active_persistent_size(struct cxl_cmd *cmd) { struct cxl_cmd_get_partition *c; c = cmd_to_get_partition(cmd); if (!c) return ULLONG_MAX; return cxl_capacity_to_bytes(c->active_persistent); } CXL_EXPORT unsigned long long cxl_cmd_partition_get_next_volatile_size(struct cxl_cmd *cmd) { struct cxl_cmd_get_partition *c; c = cmd_to_get_partition(cmd); if (!c) return ULLONG_MAX; return cxl_capacity_to_bytes(c->next_volatile); } CXL_EXPORT unsigned long long cxl_cmd_partition_get_next_persistent_size(struct cxl_cmd *cmd) { struct cxl_cmd_get_partition *c; c = cmd_to_get_partition(cmd); if (!c) return ULLONG_MAX; return cxl_capacity_to_bytes(c->next_persistent); } CXL_EXPORT int cxl_cmd_partition_set_mode(struct cxl_cmd *cmd, enum cxl_setpartition_mode mode) { struct cxl_cmd_set_partition *setpart = cmd->input_payload; if (mode == CXL_SETPART_IMMEDIATE) setpart->flags = CXL_CMD_SET_PARTITION_FLAG_IMMEDIATE; else setpart->flags = !CXL_CMD_SET_PARTITION_FLAG_IMMEDIATE; return 0; } CXL_EXPORT struct cxl_cmd *cxl_cmd_new_set_partition(struct cxl_memdev *memdev, unsigned long long volatile_size) { struct cxl_cmd_set_partition *setpart; struct cxl_cmd *cmd; cmd = cxl_cmd_new_generic(memdev, CXL_MEM_COMMAND_ID_SET_PARTITION_INFO); setpart = cmd->input_payload; setpart->volatile_size = cpu_to_le64(volatile_size) / CXL_CAPACITY_MULTIPLIER; return cmd; } CXL_EXPORT struct cxl_cmd *cxl_cmd_new_get_fw_info(struct cxl_memdev *memdev) { return cxl_cmd_new_generic(memdev, CXL_MEM_COMMAND_ID_GET_FW_INFO); } static struct cxl_cmd_get_fw_info *cmd_to_get_fw_info(struct cxl_cmd *cmd) { if (cxl_cmd_validate_status(cmd, CXL_MEM_COMMAND_ID_GET_FW_INFO)) return NULL; return cmd->output_payload; } CXL_EXPORT unsigned int cxl_cmd_fw_info_get_num_slots(struct cxl_cmd *cmd) { struct cxl_cmd_get_fw_info *c = cmd_to_get_fw_info(cmd); if (!c) return 0; return c->num_slots; } CXL_EXPORT unsigned int cxl_cmd_fw_info_get_active_slot(struct cxl_cmd *cmd) { struct cxl_cmd_get_fw_info *c = cmd_to_get_fw_info(cmd); if (!c) return 0; return c->slot_info & CXL_FW_INFO_CUR_SLOT_MASK; } CXL_EXPORT unsigned int cxl_cmd_fw_info_get_staged_slot(struct cxl_cmd *cmd) { struct cxl_cmd_get_fw_info *c = cmd_to_get_fw_info(cmd); if (!c) return 0; return (c->slot_info & CXL_FW_INFO_NEXT_SLOT_MASK) >> CXL_FW_INFO_NEXT_SLOT_SHIFT; } CXL_EXPORT bool cxl_cmd_fw_info_get_online_activate_capable(struct cxl_cmd *cmd) { struct cxl_cmd_get_fw_info *c = cmd_to_get_fw_info(cmd); if (!c) return false; return !!(c->activation_cap & CXL_FW_INFO_HAS_LIVE_ACTIVATE); } CXL_EXPORT int cxl_cmd_fw_info_get_fw_ver(struct cxl_cmd *cmd, int slot, char *buf, unsigned int len) { struct cxl_cmd_get_fw_info *c = cmd_to_get_fw_info(cmd); char *fw_ver; if (!c) return -ENXIO; if (!len) return -EINVAL; switch(slot) { case 1: fw_ver = c->slot_1_revision; break; case 2: fw_ver = c->slot_2_revision; break; case 3: fw_ver = c->slot_3_revision; break; case 4: fw_ver = c->slot_4_revision; break; default: return -EINVAL; } if (fw_ver[0] == 0) return -ENOENT; memcpy(buf, fw_ver, min(len, (unsigned int)CXL_FW_VERSION_STR_LEN)); return 0; } CXL_EXPORT int cxl_cmd_submit(struct cxl_cmd *cmd) { struct cxl_memdev *memdev = cmd->memdev; const char *devname = cxl_memdev_get_devname(memdev); struct cxl_ctx *ctx = cxl_memdev_get_ctx(memdev); int rc; switch (cmd->query_status) { case CXL_CMD_QUERY_OK: break; case CXL_CMD_QUERY_UNSUPPORTED: return -EOPNOTSUPP; case CXL_CMD_QUERY_NOT_RUN: return -EINVAL; default: err(ctx, "%s: Unknown query_status %d\n", devname, cmd->query_status); return -EINVAL; } dbg(ctx, "%s: submitting SEND cmd: in: %d, out: %d\n", devname, cmd->send_cmd->in.size, cmd->send_cmd->out.size); rc = do_cmd(cmd, CXL_MEM_SEND_COMMAND); cmd->status = cmd->send_cmd->retval; dbg(ctx, "%s: got SEND cmd: in: %d, out: %d, retval: %d, status: %d\n", devname, cmd->send_cmd->in.size, cmd->send_cmd->out.size, rc, cmd->status); return rc; } CXL_EXPORT int cxl_cmd_get_mbox_status(struct cxl_cmd *cmd) { return cmd->status; } CXL_EXPORT int cxl_cmd_get_out_size(struct cxl_cmd *cmd) { return cmd->send_cmd->out.size; } CXL_EXPORT struct cxl_cmd *cxl_cmd_new_write_label(struct cxl_memdev *memdev, void *lsa_buf, unsigned int offset, unsigned int length) { struct cxl_ctx *ctx = cxl_memdev_get_ctx(memdev); struct cxl_cmd_set_lsa *set_lsa; struct cxl_cmd *cmd; int rc; cmd = cxl_cmd_new_generic(memdev, CXL_MEM_COMMAND_ID_SET_LSA); if (!cmd) return NULL; /* this will allocate 'in.payload' */ rc = cxl_cmd_set_input_payload(cmd, NULL, sizeof(*set_lsa) + length); if (rc) { err(ctx, "%s: cmd setup failed: %s\n", cxl_memdev_get_devname(memdev), strerror(-rc)); goto out_fail; } set_lsa = (struct cxl_cmd_set_lsa *)cmd->send_cmd->in.payload; set_lsa->offset = cpu_to_le32(offset); memcpy(set_lsa->lsa_data, lsa_buf, length); return cmd; out_fail: cxl_cmd_unref(cmd); return NULL; } enum lsa_op { LSA_OP_GET, LSA_OP_SET, LSA_OP_ZERO, }; static int __lsa_op(struct cxl_memdev *memdev, int op, void *buf, size_t length, size_t offset) { const char *devname = cxl_memdev_get_devname(memdev); struct cxl_ctx *ctx = cxl_memdev_get_ctx(memdev); void *zero_buf = NULL; struct cxl_cmd *cmd; ssize_t ret_len; int rc = 0; switch (op) { case LSA_OP_GET: cmd = cxl_cmd_new_read_label(memdev, offset, length); if (!cmd) return -ENOMEM; rc = cxl_cmd_set_output_payload(cmd, buf, length); if (rc) { err(ctx, "%s: cmd setup failed: %s\n", cxl_memdev_get_devname(memdev), strerror(-rc)); goto out; } break; case LSA_OP_ZERO: zero_buf = calloc(1, length); if (!zero_buf) return -ENOMEM; buf = zero_buf; /* fall through */ case LSA_OP_SET: cmd = cxl_cmd_new_write_label(memdev, buf, offset, length); if (!cmd) { rc = -ENOMEM; goto out_free; } break; default: return -EOPNOTSUPP; } rc = cxl_cmd_submit(cmd); if (rc < 0) { err(ctx, "%s: cmd submission failed: %s\n", devname, strerror(-rc)); goto out; } rc = cxl_cmd_get_mbox_status(cmd); if (rc != 0) { err(ctx, "%s: firmware status: %d\n", devname, rc); rc = -ENXIO; goto out; } if (op == LSA_OP_GET) { ret_len = cxl_cmd_read_label_get_payload(cmd, buf, length); if (ret_len < 0) { rc = ret_len; goto out; } } out: cxl_cmd_unref(cmd); out_free: free(zero_buf); return rc; } static int lsa_op(struct cxl_memdev *memdev, int op, void *buf, size_t length, size_t offset) { const char *devname = cxl_memdev_get_devname(memdev); struct cxl_ctx *ctx = cxl_memdev_get_ctx(memdev); size_t remaining = length, cur_len, cur_off = 0; int label_iter_max, rc = 0; if (op != LSA_OP_ZERO && buf == NULL) { err(ctx, "%s: LSA buffer cannot be NULL\n", devname); return -EINVAL; } if (length == 0) return 0; if (memdev->payload_max < 0) return -EINVAL; label_iter_max = memdev->payload_max - sizeof(struct cxl_cmd_set_lsa); while (remaining) { cur_len = min((size_t)label_iter_max, remaining); rc = __lsa_op(memdev, op, buf + cur_off, cur_len, offset + cur_off); if (rc) break; remaining -= cur_len; cur_off += cur_len; } if (rc && (op == LSA_OP_SET)) err(ctx, "%s: labels may be in an inconsistent state\n", devname); return rc; } CXL_EXPORT int cxl_memdev_zero_label(struct cxl_memdev *memdev, size_t length, size_t offset) { return lsa_op(memdev, LSA_OP_ZERO, NULL, length, offset); } CXL_EXPORT int cxl_memdev_write_label(struct cxl_memdev *memdev, void *buf, size_t length, size_t offset) { return lsa_op(memdev, LSA_OP_SET, buf, length, offset); } CXL_EXPORT int cxl_memdev_read_label(struct cxl_memdev *memdev, void *buf, size_t length, size_t offset) { return lsa_op(memdev, LSA_OP_GET, buf, length, offset); } #define cxl_alert_config_set_field(field) \ CXL_EXPORT int cxl_cmd_alert_config_set_##field(struct cxl_cmd *cmd, int val) \ { \ struct cxl_cmd_set_alert_config *setalert = cmd->input_payload; \ setalert->field = val; \ return 0; \ } cxl_alert_config_set_field(life_used_prog_warn_threshold) cxl_alert_config_set_field(dev_over_temperature_prog_warn_threshold) cxl_alert_config_set_field(dev_under_temperature_prog_warn_threshold) cxl_alert_config_set_field(corrected_volatile_mem_err_prog_warn_threshold) cxl_alert_config_set_field(corrected_pmem_err_prog_warn_threshold) cxl_alert_config_set_field(valid_alert_actions) cxl_alert_config_set_field(enable_alert_actions) CXL_EXPORT struct cxl_cmd *cxl_cmd_new_set_alert_config(struct cxl_memdev *memdev) { return cxl_cmd_new_generic(memdev, CXL_MEM_COMMAND_ID_SET_ALERT_CONFIG); } ndctl-81/cxl/lib/libcxl.pc.in000066400000000000000000000003211476737544500161740ustar00rootroot00000000000000prefix=@prefix@ exec_prefix=@exec_prefix@ libdir=@libdir@ includedir=@includedir@ Name: libcxl Description: Manage CXL devices Version: @VERSION@ Libs: -L${libdir} -lcxl Libs.private: Cflags: -I${includedir} ndctl-81/cxl/lib/libcxl.sym000066400000000000000000000206301476737544500160020ustar00rootroot00000000000000LIBCXL_1 { global: cxl_get_userdata; cxl_set_userdata; cxl_get_private_data; cxl_set_private_data; cxl_ref; cxl_get_log_priority; cxl_set_log_fn; cxl_unref; cxl_set_log_priority; cxl_new; cxl_memdev_get_first; cxl_memdev_get_next; cxl_memdev_get_id; cxl_memdev_get_devname; cxl_memdev_get_major; cxl_memdev_get_minor; cxl_memdev_get_ctx; cxl_memdev_get_pmem_size; cxl_memdev_get_ram_size; cxl_memdev_get_firmware_verison; cxl_cmd_get_devname; cxl_cmd_new_raw; cxl_cmd_set_input_payload; cxl_cmd_set_output_payload; cxl_cmd_ref; cxl_cmd_unref; cxl_cmd_submit; cxl_cmd_get_mbox_status; cxl_cmd_get_out_size; cxl_cmd_new_identify; cxl_cmd_identify_get_fw_rev; cxl_cmd_identify_get_partition_align; cxl_cmd_identify_get_label_size; cxl_cmd_new_get_health_info; cxl_cmd_health_info_get_maintenance_needed; cxl_cmd_health_info_get_performance_degraded; cxl_cmd_health_info_get_hw_replacement_needed; cxl_cmd_health_info_get_media_normal; cxl_cmd_health_info_get_media_not_ready; cxl_cmd_health_info_get_media_persistence_lost; cxl_cmd_health_info_get_media_data_lost; cxl_cmd_health_info_get_media_powerloss_persistence_loss; cxl_cmd_health_info_get_media_shutdown_persistence_loss; cxl_cmd_health_info_get_media_persistence_loss_imminent; cxl_cmd_health_info_get_media_powerloss_data_loss; cxl_cmd_health_info_get_media_shutdown_data_loss; cxl_cmd_health_info_get_media_data_loss_imminent; cxl_cmd_health_info_get_ext_life_used_normal; cxl_cmd_health_info_get_ext_life_used_warning; cxl_cmd_health_info_get_ext_life_used_critical; cxl_cmd_health_info_get_ext_temperature_normal; cxl_cmd_health_info_get_ext_temperature_warning; cxl_cmd_health_info_get_ext_temperature_critical; cxl_cmd_health_info_get_ext_corrected_volatile_normal; cxl_cmd_health_info_get_ext_corrected_volatile_warning; cxl_cmd_health_info_get_ext_corrected_persistent_normal; cxl_cmd_health_info_get_ext_corrected_persistent_warning; cxl_cmd_health_info_get_life_used; cxl_cmd_health_info_get_temperature; cxl_cmd_health_info_get_dirty_shutdowns; cxl_cmd_health_info_get_volatile_errors; cxl_cmd_health_info_get_pmem_errors; cxl_cmd_new_read_label; cxl_cmd_read_label_get_payload; cxl_memdev_get_label_size; cxl_memdev_nvdimm_bridge_active; cxl_cmd_new_write_label; cxl_memdev_zero_label; cxl_memdev_write_label; cxl_memdev_read_label; local: *; }; LIBCXL_2 { global: cxl_memdev_get_serial; cxl_memdev_get_numa_node; cxl_memdev_get_host; cxl_bus_get_first; cxl_bus_get_next; cxl_bus_get_provider; cxl_bus_get_devname; cxl_bus_get_id; cxl_bus_get_port; cxl_bus_get_ctx; cxl_bus_disable_invalidate; cxl_port_get_first; cxl_port_get_next; cxl_port_get_devname; cxl_port_get_id; cxl_port_get_ctx; cxl_port_is_enabled; cxl_port_get_parent; cxl_port_is_root; cxl_port_is_switch; cxl_port_get_depth; cxl_port_to_bus; cxl_port_is_endpoint; cxl_port_to_endpoint; cxl_port_get_bus; cxl_port_get_host; cxl_port_get_bus; cxl_port_hosts_memdev; cxl_port_get_nr_dports; cxl_port_disable_invalidate; cxl_port_enable; cxl_port_get_next_all; cxl_endpoint_get_first; cxl_endpoint_get_next; cxl_endpoint_get_devname; cxl_endpoint_get_id; cxl_endpoint_get_ctx; cxl_endpoint_is_enabled; cxl_endpoint_get_parent; cxl_endpoint_get_port; cxl_endpoint_get_host; cxl_endpoint_get_memdev; cxl_endpoint_get_bus; cxl_memdev_get_endpoint; cxl_memdev_is_enabled; cxl_memdev_get_bus; cxl_memdev_disable_invalidate; cxl_memdev_enable; cxl_decoder_get_first; cxl_decoder_get_next; cxl_decoder_get_ctx; cxl_decoder_get_id; cxl_decoder_get_port; cxl_decoder_get_resource; cxl_decoder_get_size; cxl_decoder_get_devname; cxl_decoder_get_target_by_memdev; cxl_decoder_get_target_by_position; cxl_decoder_get_nr_targets; cxl_decoder_get_target_type; cxl_decoder_is_pmem_capable; cxl_decoder_is_volatile_capable; cxl_decoder_is_mem_capable; cxl_decoder_is_accelmem_capable; cxl_decoder_is_locked; cxl_decoder_create_pmem_region; cxl_target_get_first; cxl_target_get_next; cxl_target_get_decoder; cxl_target_get_position; cxl_target_get_id; cxl_target_get_devname; cxl_target_maps_memdev; cxl_target_get_physical_node; cxl_dport_get_first; cxl_dport_get_next; cxl_dport_get_devname; cxl_dport_get_physical_node; cxl_dport_get_id; cxl_dport_get_port; cxl_port_get_dport_by_memdev; cxl_dport_maps_memdev; cxl_cmd_new_get_partition; cxl_cmd_partition_get_active_volatile_size; cxl_cmd_partition_get_active_persistent_size; cxl_cmd_partition_get_next_volatile_size; cxl_cmd_partition_get_next_persistent_size; cxl_cmd_identify_get_total_size; cxl_cmd_identify_get_volatile_only_size; cxl_cmd_identify_get_persistent_only_size; cxl_cmd_new_set_partition; cxl_cmd_partition_set_mode; } LIBCXL_1; LIBCXL_3 { global: cxl_decoder_get_dpa_resource; cxl_decoder_get_dpa_size; cxl_decoder_get_mode; cxl_decoder_get_last; cxl_decoder_get_prev; cxl_decoder_set_dpa_size; cxl_decoder_set_mode; cxl_region_get_first; cxl_region_get_next; cxl_region_decode_is_committed; cxl_region_is_enabled; cxl_region_disable; cxl_region_enable; cxl_region_delete; cxl_region_get_ctx; cxl_region_get_decoder; cxl_region_get_id; cxl_region_get_devname; cxl_region_get_uuid; cxl_region_get_size; cxl_region_get_resource; cxl_region_get_interleave_ways; cxl_region_get_interleave_granularity; cxl_region_get_target_decoder; cxl_region_set_size; cxl_region_set_uuid; cxl_region_set_interleave_ways; cxl_region_set_interleave_granularity; cxl_region_set_target; cxl_region_clear_target; cxl_region_clear_all_targets; cxl_region_decode_commit; cxl_region_decode_reset; cxl_mapping_get_first; cxl_mapping_get_next; cxl_mapping_get_decoder; cxl_mapping_get_position; cxl_decoder_get_by_name; cxl_decoder_get_memdev; cxl_decoder_get_interleave_granularity; cxl_decoder_get_interleave_ways; cxl_decoder_get_max_available_extent; cxl_decoder_get_region; } LIBCXL_2; LIBCXL_4 { global: cxl_cmd_new_get_alert_config; cxl_cmd_alert_config_life_used_prog_warn_threshold_valid; cxl_cmd_alert_config_dev_over_temperature_prog_warn_threshold_valid; cxl_cmd_alert_config_dev_under_temperature_prog_warn_threshold_valid; cxl_cmd_alert_config_corrected_volatile_mem_err_prog_warn_threshold_valid; cxl_cmd_alert_config_corrected_pmem_err_prog_warn_threshold_valid; cxl_cmd_alert_config_life_used_prog_warn_threshold_writable; cxl_cmd_alert_config_dev_over_temperature_prog_warn_threshold_writable; cxl_cmd_alert_config_dev_under_temperature_prog_warn_threshold_writable; cxl_cmd_alert_config_corrected_volatile_mem_err_prog_warn_threshold_writable; cxl_cmd_alert_config_corrected_pmem_err_prog_warn_threshold_writable; cxl_cmd_alert_config_get_life_used_crit_alert_threshold; cxl_cmd_alert_config_get_life_used_prog_warn_threshold; cxl_cmd_alert_config_get_dev_over_temperature_crit_alert_threshold; cxl_cmd_alert_config_get_dev_under_temperature_crit_alert_threshold; cxl_cmd_alert_config_get_dev_over_temperature_prog_warn_threshold; cxl_cmd_alert_config_get_dev_under_temperature_prog_warn_threshold; cxl_cmd_alert_config_get_corrected_volatile_mem_err_prog_warn_threshold; cxl_cmd_alert_config_get_corrected_pmem_err_prog_warn_threshold; cxl_target_get_firmware_node; cxl_dport_get_firmware_node; } LIBCXL_3; LIBCXL_5 { global: cxl_region_get_mode; cxl_decoder_create_ram_region; cxl_region_get_daxctl_region; cxl_port_get_parent_dport; } LIBCXL_4; LIBCXL_6 { global: cxl_cmd_new_get_fw_info; cxl_cmd_fw_info_get_num_slots; cxl_cmd_fw_info_get_active_slot; cxl_cmd_fw_info_get_staged_slot; cxl_cmd_fw_info_get_online_activate_capable; cxl_cmd_fw_info_get_fw_ver; cxl_memdev_fw_update_in_progress; cxl_memdev_fw_update_get_remaining; cxl_memdev_update_fw; cxl_memdev_cancel_fw_update; } LIBCXL_5; LIBCXL_7 { global: cxl_cmd_alert_config_set_life_used_prog_warn_threshold; cxl_cmd_alert_config_set_dev_over_temperature_prog_warn_threshold; cxl_cmd_alert_config_set_dev_under_temperature_prog_warn_threshold; cxl_cmd_alert_config_set_corrected_volatile_mem_err_prog_warn_threshold; cxl_cmd_alert_config_set_corrected_pmem_err_prog_warn_threshold; cxl_cmd_alert_config_set_valid_alert_actions; cxl_cmd_alert_config_set_enable_alert_actions; cxl_cmd_new_set_alert_config; cxl_memdev_wait_sanitize; cxl_root_decoder_get_qos_class; cxl_memdev_get_pmem_qos_class; cxl_memdev_get_ram_qos_class; cxl_region_qos_class_mismatch; cxl_port_decoders_committed; } LIBCXL_6; LIBECXL_8 { global: cxl_memdev_trigger_poison_list; cxl_region_trigger_poison_list; } LIBCXL_7; ndctl-81/cxl/lib/meson.build000066400000000000000000000015201476737544500161320ustar00rootroot00000000000000libcxl_version = '@0@.@1@.@2@'.format( LIBCXL_CURRENT - LIBCXL_AGE, LIBCXL_REVISION, LIBCXL_AGE) libcxl_dir_path = meson.current_source_dir() libcxl_sym = files('libcxl.sym') libcxl_sym_path = libcxl_dir_path / 'libcxl.sym' cxl = library('cxl', '../../util/sysfs.c', '../../util/log.c', '../../util/log.h', 'libcxl.c', include_directories : root_inc, dependencies : [ uuid, kmod, libudev, daxctl_dep, ], version : libcxl_version, install : true, install_dir : rootlibdir, link_args : '-Wl,--version-script=' + libcxl_sym_path, link_depends : libcxl_sym, ) cxl_dep = declare_dependency(link_with : cxl) custom_target( 'libcxl.pc', command : pkgconfig_script + [ '@INPUT@' ], input : 'libcxl.pc.in', output : 'libcxl.pc', capture : true, install : true, install_dir : pkgconfiglibdir, ) ndctl-81/cxl/lib/private.h000066400000000000000000000242351476737544500156230ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1 */ /* Copyright (C) 2020-2021, Intel Corporation. All rights reserved. */ #ifndef _LIBCXL_PRIVATE_H_ #define _LIBCXL_PRIVATE_H_ #include #include #include #include #include #include #include #define CXL_EXPORT __attribute__ ((visibility("default"))) struct cxl_pmem { int id; void *dev_buf; size_t buf_len; char *dev_path; }; struct cxl_fw_loader { char *dev_path; char *loading; char *data; char *remaining; char *cancel; char *status; }; enum cxl_fwl_loading { CXL_FWL_LOADING_END = 0, CXL_FWL_LOADING_START, }; struct cxl_endpoint; struct cxl_memdev { int id, major, minor; int numa_node; void *dev_buf; size_t buf_len; char *host_path; char *dev_path; char *firmware_version; struct cxl_ctx *ctx; struct list_node list; unsigned long long pmem_size; unsigned long long ram_size; int ram_qos_class; int pmem_qos_class; int payload_max; size_t lsa_size; struct kmod_module *module; struct cxl_pmem *pmem; unsigned long long serial; struct cxl_endpoint *endpoint; struct cxl_fw_loader *fwl; }; struct cxl_dport { int id; void *dev_buf; size_t buf_len; char *dev_path; char *phys_path; char *fw_path; struct cxl_port *port; struct list_node list; }; enum cxl_port_type { CXL_PORT_ROOT, CXL_PORT_SWITCH, CXL_PORT_ENDPOINT, }; struct cxl_port { int id; void *dev_buf; size_t buf_len; char *dev_path; char *uport; char *parent_dport_path; struct cxl_dport *parent_dport; int ports_init; int endpoints_init; int decoders_init; int dports_init; int nr_dports; int depth; int decoders_committed; struct cxl_ctx *ctx; struct cxl_bus *bus; enum cxl_port_type type; struct cxl_port *parent; struct kmod_module *module; struct list_node list; struct list_head child_ports; struct list_head endpoints; struct list_head decoders; struct list_head dports; }; struct cxl_bus { struct cxl_port port; }; struct cxl_endpoint { struct cxl_port port; struct cxl_memdev *memdev; }; struct cxl_target { struct list_node list; struct cxl_decoder *decoder; char *dev_path; char *phys_path; char *fw_path; int id, position; }; struct cxl_decoder { struct cxl_port *port; struct list_node list; struct cxl_ctx *ctx; u64 start; u64 size; u64 dpa_resource; u64 dpa_size; u64 max_available_extent; void *dev_buf; size_t buf_len; char *dev_path; int nr_targets; int id; enum cxl_decoder_mode mode; unsigned int interleave_ways; unsigned int interleave_granularity; bool pmem_capable; bool volatile_capable; bool mem_capable; bool accelmem_capable; bool locked; enum cxl_decoder_target_type target_type; int regions_init; struct list_head targets; struct list_head regions; struct list_head stale_regions; int qos_class; }; enum cxl_decode_state { CXL_DECODE_UNKNOWN = -1, CXL_DECODE_RESET = 0, CXL_DECODE_COMMIT, }; struct cxl_region { struct cxl_decoder *decoder; struct list_node list; int mappings_init; struct cxl_ctx *ctx; void *dev_buf; size_t buf_len; char *dev_path; int id; uuid_t uuid; u64 start; u64 size; unsigned int interleave_ways; unsigned int interleave_granularity; enum cxl_decode_state decode_state; enum cxl_decoder_mode mode; struct daxctl_region *dax_region; struct kmod_module *module; struct list_head mappings; }; struct cxl_memdev_mapping { struct cxl_region *region; struct cxl_decoder *decoder; unsigned int position; struct list_node list; }; enum cxl_cmd_query_status { CXL_CMD_QUERY_NOT_RUN = 0, CXL_CMD_QUERY_OK, CXL_CMD_QUERY_UNSUPPORTED, }; /** * struct cxl_cmd - CXL memdev command * @memdev: the memory device to which the command is being sent * @query_cmd: structure for the Linux 'Query commands' ioctl * @send_cmd: structure for the Linux 'Send command' ioctl * @input_payload: buffer for input payload managed by libcxl * @output_payload: buffer for output payload managed by libcxl * @refcount: reference for passing command buffer around * @query_status: status from query_commands * @query_idx: index of 'this' command in the query_commands array * @status: command return status from the device */ struct cxl_cmd { struct cxl_memdev *memdev; struct cxl_mem_query_commands *query_cmd; struct cxl_send_command *send_cmd; void *input_payload; void *output_payload; int refcount; int query_status; int query_idx; int status; }; #define CXL_CMD_IDENTIFY_FW_REV_LENGTH 0x10 struct cxl_cmd_identify { char fw_revision[CXL_CMD_IDENTIFY_FW_REV_LENGTH]; le64 total_capacity; le64 volatile_capacity; le64 persistent_capacity; le64 partition_align; le16 info_event_log_size; le16 warning_event_log_size; le16 failure_event_log_size; le16 fatal_event_log_size; le32 lsa_size; u8 poison_list_max_mer[3]; le16 inject_poison_limit; u8 poison_caps; u8 qos_telemetry_caps; } __attribute__((packed)); struct cxl_cmd_get_lsa_in { le32 offset; le32 length; } __attribute__((packed)); struct cxl_cmd_set_lsa { le32 offset; le32 rsvd; unsigned char lsa_data[0]; } __attribute__ ((packed)); struct cxl_cmd_get_health_info { u8 health_status; u8 media_status; u8 ext_status; u8 life_used; le16 temperature; le32 dirty_shutdowns; le32 volatile_errors; le32 pmem_errors; } __attribute__((packed)); /* CXL 3.0 8.2.9.3.1 Get Firmware Info */ struct cxl_cmd_get_fw_info { u8 num_slots; u8 slot_info; u8 activation_cap; u8 reserved[13]; char slot_1_revision[0x10]; char slot_2_revision[0x10]; char slot_3_revision[0x10]; char slot_4_revision[0x10]; } __attribute__((packed)); #define CXL_FW_INFO_CUR_SLOT_MASK GENMASK(2, 0) #define CXL_FW_INFO_NEXT_SLOT_MASK GENMASK(5, 3) #define CXL_FW_INFO_NEXT_SLOT_SHIFT (3) #define CXL_FW_INFO_HAS_LIVE_ACTIVATE BIT(0) #define CXL_FW_VERSION_STR_LEN 16 #define CXL_FW_MAX_SLOTS 4 /* CXL 3.0 8.2.9.8.3.2 Get Alert Configuration */ struct cxl_cmd_get_alert_config { u8 valid_alerts; u8 programmable_alerts; u8 life_used_crit_alert_threshold; u8 life_used_prog_warn_threshold; le16 dev_over_temperature_crit_alert_threshold; le16 dev_under_temperature_crit_alert_threshold; le16 dev_over_temperature_prog_warn_threshold; le16 dev_under_temperature_prog_warn_threshold; le16 corrected_volatile_mem_err_prog_warn_threshold; le16 corrected_pmem_err_prog_warn_threshold; } __attribute__((packed)); /* CXL 3.0 8.2.9.8.3.2 Get Alert Configuration Byte 0 Valid Alerts */ #define CXL_CMD_ALERT_CONFIG_VALID_ALERTS_LIFE_USED_PROG_WARN_THRESHOLD_MASK \ BIT(0) #define CXL_CMD_ALERT_CONFIG_VALID_ALERTS_DEV_OVER_TEMPERATURE_PROG_WARN_THRESHOLD_MASK \ BIT(1) #define CXL_CMD_ALERT_CONFIG_VALID_ALERTS_DEV_UNDER_TEMPERATURE_PROG_WARN_THRESHOLD_MASK \ BIT(2) #define CXL_CMD_ALERT_CONFIG_VALID_ALERTS_CORRECTED_VOLATILE_MEM_ERR_PROG_WARN_THRESHOLD_MASK \ BIT(3) #define CXL_CMD_ALERT_CONFIG_VALID_ALERTS_CORRECTED_PMEM_ERR_PROG_WARN_THRESHOLD_MASK \ BIT(4) /* CXL 3.0 8.2.9.8.3.2 Get Alert Configuration Byte 1 Programmable Alerts */ #define CXL_CMD_ALERT_CONFIG_PROG_ALERTS_LIFE_USED_PROG_WARN_THRESHOLD_MASK \ BIT(0) #define CXL_CMD_ALERT_CONFIG_PROG_ALERTS_DEV_OVER_TEMPERATURE_PROG_WARN_THRESHOLD_MASK \ BIT(1) #define CXL_CMD_ALERT_CONFIG_PROG_ALERTS_DEV_UNDER_TEMPERATURE_PROG_WARN_THRESHOLD_MASK \ BIT(2) #define CXL_CMD_ALERT_CONFIG_PROG_ALERTS_CORRECTED_VOLATILE_MEM_ERR_PROG_WARN_THRESHOLD_MASK \ BIT(3) #define CXL_CMD_ALERT_CONFIG_PROG_ALERTS_CORRECTED_PMEM_ERR_PROG_WARN_THRESHOLD_MASK \ BIT(4) /* CXL 3.0 8.2.9.8.3.3 Set Alert Configuration */ struct cxl_cmd_set_alert_config { u8 valid_alert_actions; u8 enable_alert_actions; u8 life_used_prog_warn_threshold; u8 rsvd; le16 dev_over_temperature_prog_warn_threshold; le16 dev_under_temperature_prog_warn_threshold; le16 corrected_volatile_mem_err_prog_warn_threshold; le16 corrected_pmem_err_prog_warn_threshold; } __attribute__((packed)); struct cxl_cmd_get_partition { le64 active_volatile; le64 active_persistent; le64 next_volatile; le64 next_persistent; } __attribute__((packed)); #define CXL_CAPACITY_MULTIPLIER SZ_256M struct cxl_cmd_set_partition { le64 volatile_size; u8 flags; } __attribute__((packed)); /* CXL 2.0 8.2.9.5.2 Set Partition Info */ #define CXL_CMD_SET_PARTITION_FLAG_IMMEDIATE BIT(0) /* CXL 2.0 8.2.9.5.3 Byte 0 Health Status */ #define CXL_CMD_HEALTH_INFO_STATUS_MAINTENANCE_NEEDED_MASK BIT(0) #define CXL_CMD_HEALTH_INFO_STATUS_PERFORMANCE_DEGRADED_MASK BIT(1) #define CXL_CMD_HEALTH_INFO_STATUS_HW_REPLACEMENT_NEEDED_MASK BIT(2) /* CXL 2.0 8.2.9.5.3 Byte 1 Media Status */ #define CXL_CMD_HEALTH_INFO_MEDIA_STATUS_NORMAL 0x0 #define CXL_CMD_HEALTH_INFO_MEDIA_STATUS_NOT_READY 0x1 #define CXL_CMD_HEALTH_INFO_MEDIA_STATUS_PERSISTENCE_LOST 0x2 #define CXL_CMD_HEALTH_INFO_MEDIA_STATUS_DATA_LOST 0x3 #define CXL_CMD_HEALTH_INFO_MEDIA_STATUS_POWERLOSS_PERSISTENCE_LOSS 0x4 #define CXL_CMD_HEALTH_INFO_MEDIA_STATUS_SHUTDOWN_PERSISTENCE_LOSS 0x5 #define CXL_CMD_HEALTH_INFO_MEDIA_STATUS_PERSISTENCE_LOSS_IMMINENT 0x6 #define CXL_CMD_HEALTH_INFO_MEDIA_STATUS_POWERLOSS_DATA_LOSS 0x7 #define CXL_CMD_HEALTH_INFO_MEDIA_STATUS_SHUTDOWN_DATA_LOSS 0x8 #define CXL_CMD_HEALTH_INFO_MEDIA_STATUS_DATA_LOSS_IMMINENT 0x9 /* CXL 2.0 8.2.9.5.3 Byte 2 Additional Status */ #define CXL_CMD_HEALTH_INFO_EXT_LIFE_USED_MASK GENMASK(1, 0) #define CXL_CMD_HEALTH_INFO_EXT_LIFE_USED_NORMAL (0) #define CXL_CMD_HEALTH_INFO_EXT_LIFE_USED_WARNING (1) #define CXL_CMD_HEALTH_INFO_EXT_LIFE_USED_CRITICAL (2) #define CXL_CMD_HEALTH_INFO_EXT_TEMPERATURE_MASK GENMASK(3, 2) #define CXL_CMD_HEALTH_INFO_EXT_TEMPERATURE_NORMAL (0) #define CXL_CMD_HEALTH_INFO_EXT_TEMPERATURE_WARNING (1) #define CXL_CMD_HEALTH_INFO_EXT_TEMPERATURE_CRITICAL (2) #define CXL_CMD_HEALTH_INFO_EXT_CORRECTED_VOLATILE_MASK BIT(4) #define CXL_CMD_HEALTH_INFO_EXT_CORRECTED_VOLATILE_NORMAL (0) #define CXL_CMD_HEALTH_INFO_EXT_CORRECTED_VOLATILE_WARNING (1) #define CXL_CMD_HEALTH_INFO_EXT_CORRECTED_PERSISTENT_MASK BIT(5) #define CXL_CMD_HEALTH_INFO_EXT_CORRECTED_PERSISTENT_NORMAL (0) #define CXL_CMD_HEALTH_INFO_EXT_CORRECTED_PERSISTENT_WARNING (1) #define CXL_CMD_HEALTH_INFO_LIFE_USED_NOT_IMPL 0xff #define CXL_CMD_HEALTH_INFO_TEMPERATURE_NOT_IMPL 0xffff static inline int check_kmod(struct kmod_ctx *kmod_ctx) { return kmod_ctx ? 0 : -ENXIO; } #endif /* _LIBCXL_PRIVATE_H_ */ ndctl-81/cxl/libcxl.h000066400000000000000000000562121476737544500146600ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1 */ /* Copyright (C) 2020-2021, Intel Corporation. All rights reserved. */ #ifndef _LIBCXL_H_ #define _LIBCXL_H_ #include #include #include #include #ifdef HAVE_UUID #include #else typedef unsigned char uuid_t[16]; #endif #ifdef __cplusplus extern "C" { #endif struct cxl_ctx; struct cxl_ctx *cxl_ref(struct cxl_ctx *ctx); void cxl_unref(struct cxl_ctx *ctx); int cxl_new(struct cxl_ctx **ctx); void cxl_set_log_fn(struct cxl_ctx *ctx, void (*log_fn)(struct cxl_ctx *ctx, int priority, const char *file, int line, const char *fn, const char *format, va_list args)); int cxl_get_log_priority(struct cxl_ctx *ctx); void cxl_set_log_priority(struct cxl_ctx *ctx, int priority); void cxl_set_userdata(struct cxl_ctx *ctx, void *userdata); void *cxl_get_userdata(struct cxl_ctx *ctx); void cxl_set_private_data(struct cxl_ctx *ctx, void *data); void *cxl_get_private_data(struct cxl_ctx *ctx); enum cxl_fwl_status { CXL_FWL_STATUS_UNKNOWN, CXL_FWL_STATUS_IDLE, CXL_FWL_STATUS_RECEIVING, CXL_FWL_STATUS_PREPARING, CXL_FWL_STATUS_TRANSFERRING, CXL_FWL_STATUS_PROGRAMMING, }; static inline enum cxl_fwl_status cxl_fwl_status_from_ident(char *status) { if (strcmp(status, "idle") == 0) return CXL_FWL_STATUS_IDLE; if (strcmp(status, "receiving") == 0) return CXL_FWL_STATUS_RECEIVING; if (strcmp(status, "preparing") == 0) return CXL_FWL_STATUS_PREPARING; if (strcmp(status, "transferring") == 0) return CXL_FWL_STATUS_TRANSFERRING; if (strcmp(status, "programming") == 0) return CXL_FWL_STATUS_PROGRAMMING; return CXL_FWL_STATUS_UNKNOWN; } struct cxl_memdev; struct cxl_memdev *cxl_memdev_get_first(struct cxl_ctx *ctx); struct cxl_memdev *cxl_memdev_get_next(struct cxl_memdev *memdev); int cxl_memdev_get_id(struct cxl_memdev *memdev); unsigned long long cxl_memdev_get_serial(struct cxl_memdev *memdev); int cxl_memdev_get_numa_node(struct cxl_memdev *memdev); const char *cxl_memdev_get_devname(struct cxl_memdev *memdev); const char *cxl_memdev_get_host(struct cxl_memdev *memdev); struct cxl_bus *cxl_memdev_get_bus(struct cxl_memdev *memdev); int cxl_memdev_get_major(struct cxl_memdev *memdev); int cxl_memdev_get_minor(struct cxl_memdev *memdev); struct cxl_ctx *cxl_memdev_get_ctx(struct cxl_memdev *memdev); unsigned long long cxl_memdev_get_pmem_size(struct cxl_memdev *memdev); unsigned long long cxl_memdev_get_ram_size(struct cxl_memdev *memdev); int cxl_memdev_get_pmem_qos_class(struct cxl_memdev *memdev); int cxl_memdev_get_ram_qos_class(struct cxl_memdev *memdev); const char *cxl_memdev_get_firmware_verison(struct cxl_memdev *memdev); bool cxl_memdev_fw_update_in_progress(struct cxl_memdev *memdev); size_t cxl_memdev_fw_update_get_remaining(struct cxl_memdev *memdev); int cxl_memdev_update_fw(struct cxl_memdev *memdev, const char *fw_path); int cxl_memdev_cancel_fw_update(struct cxl_memdev *memdev); int cxl_memdev_wait_sanitize(struct cxl_memdev *memdev, int timeout_ms); /* ABI spelling mistakes are forever */ static inline const char *cxl_memdev_get_firmware_version( struct cxl_memdev *memdev) { return cxl_memdev_get_firmware_verison(memdev); } size_t cxl_memdev_get_label_size(struct cxl_memdev *memdev); int cxl_memdev_disable_invalidate(struct cxl_memdev *memdev); int cxl_memdev_enable(struct cxl_memdev *memdev); struct cxl_endpoint; struct cxl_endpoint *cxl_memdev_get_endpoint(struct cxl_memdev *memdev); int cxl_memdev_nvdimm_bridge_active(struct cxl_memdev *memdev); int cxl_memdev_zero_label(struct cxl_memdev *memdev, size_t length, size_t offset); int cxl_memdev_read_label(struct cxl_memdev *memdev, void *buf, size_t length, size_t offset); int cxl_memdev_write_label(struct cxl_memdev *memdev, void *buf, size_t length, size_t offset); struct cxl_cmd *cxl_cmd_new_get_fw_info(struct cxl_memdev *memdev); unsigned int cxl_cmd_fw_info_get_num_slots(struct cxl_cmd *cmd); unsigned int cxl_cmd_fw_info_get_active_slot(struct cxl_cmd *cmd); unsigned int cxl_cmd_fw_info_get_staged_slot(struct cxl_cmd *cmd); bool cxl_cmd_fw_info_get_online_activate_capable(struct cxl_cmd *cmd); int cxl_cmd_fw_info_get_fw_ver(struct cxl_cmd *cmd, int slot, char *buf, unsigned int len); #define cxl_memdev_foreach(ctx, memdev) \ for (memdev = cxl_memdev_get_first(ctx); \ memdev != NULL; \ memdev = cxl_memdev_get_next(memdev)) struct cxl_bus; struct cxl_bus *cxl_bus_get_first(struct cxl_ctx *ctx); struct cxl_bus *cxl_bus_get_next(struct cxl_bus *bus); const char *cxl_bus_get_provider(struct cxl_bus *bus); const char *cxl_bus_get_devname(struct cxl_bus *bus); int cxl_bus_get_id(struct cxl_bus *bus); struct cxl_port *cxl_bus_get_port(struct cxl_bus *bus); struct cxl_ctx *cxl_bus_get_ctx(struct cxl_bus *bus); int cxl_bus_disable_invalidate(struct cxl_bus *bus); #define cxl_bus_foreach(ctx, bus) \ for (bus = cxl_bus_get_first(ctx); bus != NULL; \ bus = cxl_bus_get_next(bus)) struct cxl_port; struct cxl_port *cxl_port_get_first(struct cxl_port *parent); struct cxl_port *cxl_port_get_next(struct cxl_port *port); const char *cxl_port_get_devname(struct cxl_port *port); int cxl_port_get_id(struct cxl_port *port); struct cxl_ctx *cxl_port_get_ctx(struct cxl_port *port); int cxl_port_is_enabled(struct cxl_port *port); struct cxl_port *cxl_port_get_parent(struct cxl_port *port); bool cxl_port_is_root(struct cxl_port *port); bool cxl_port_is_switch(struct cxl_port *port); int cxl_port_get_depth(struct cxl_port *port); struct cxl_bus *cxl_port_to_bus(struct cxl_port *port); bool cxl_port_is_endpoint(struct cxl_port *port); struct cxl_endpoint *cxl_port_to_endpoint(struct cxl_port *port); struct cxl_bus *cxl_port_get_bus(struct cxl_port *port); const char *cxl_port_get_host(struct cxl_port *port); struct cxl_dport *cxl_port_get_parent_dport(struct cxl_port *port); bool cxl_port_hosts_memdev(struct cxl_port *port, struct cxl_memdev *memdev); int cxl_port_get_nr_dports(struct cxl_port *port); int cxl_port_disable_invalidate(struct cxl_port *port); int cxl_port_enable(struct cxl_port *port); int cxl_port_decoders_committed(struct cxl_port *port); struct cxl_port *cxl_port_get_next_all(struct cxl_port *port, const struct cxl_port *top); #define cxl_port_foreach(parent, port) \ for (port = cxl_port_get_first(parent); port != NULL; \ port = cxl_port_get_next(port)) #define cxl_port_foreach_all(top, port) \ for (port = cxl_port_get_first(top); port != NULL; \ port = cxl_port_get_next_all(port, top)) struct cxl_dport; struct cxl_dport *cxl_dport_get_first(struct cxl_port *port); struct cxl_dport *cxl_dport_get_next(struct cxl_dport *dport); const char *cxl_dport_get_devname(struct cxl_dport *dport); const char *cxl_dport_get_physical_node(struct cxl_dport *dport); const char *cxl_dport_get_firmware_node(struct cxl_dport *dport); struct cxl_port *cxl_dport_get_port(struct cxl_dport *dport); int cxl_dport_get_id(struct cxl_dport *dport); bool cxl_dport_maps_memdev(struct cxl_dport *dport, struct cxl_memdev *memdev); struct cxl_dport *cxl_port_get_dport_by_memdev(struct cxl_port *port, struct cxl_memdev *memdev); #define cxl_dport_foreach(port, dport) \ for (dport = cxl_dport_get_first(port); dport != NULL; \ dport = cxl_dport_get_next(dport)) #define CXL_QOS_CLASS_NONE -1 struct cxl_decoder; struct cxl_decoder *cxl_decoder_get_first(struct cxl_port *port); struct cxl_decoder *cxl_decoder_get_next(struct cxl_decoder *decoder); struct cxl_decoder *cxl_decoder_get_last(struct cxl_port *port); struct cxl_decoder *cxl_decoder_get_prev(struct cxl_decoder *decoder); unsigned long long cxl_decoder_get_resource(struct cxl_decoder *decoder); unsigned long long cxl_decoder_get_size(struct cxl_decoder *decoder); unsigned long long cxl_decoder_get_dpa_resource(struct cxl_decoder *decoder); unsigned long long cxl_decoder_get_dpa_size(struct cxl_decoder *decoder); unsigned long long cxl_decoder_get_max_available_extent(struct cxl_decoder *decoder); int cxl_root_decoder_get_qos_class(struct cxl_decoder *decoder); enum cxl_decoder_mode { CXL_DECODER_MODE_NONE, CXL_DECODER_MODE_MIXED, CXL_DECODER_MODE_PMEM, CXL_DECODER_MODE_RAM, }; static inline const char *cxl_decoder_mode_name(enum cxl_decoder_mode mode) { static const char *names[] = { [CXL_DECODER_MODE_NONE] = "none", [CXL_DECODER_MODE_MIXED] = "mixed", [CXL_DECODER_MODE_PMEM] = "pmem", [CXL_DECODER_MODE_RAM] = "ram", }; if (mode < CXL_DECODER_MODE_NONE || mode > CXL_DECODER_MODE_RAM) mode = CXL_DECODER_MODE_NONE; return names[mode]; } static inline enum cxl_decoder_mode cxl_decoder_mode_from_ident(const char *ident) { if (strcmp(ident, "ram") == 0) return CXL_DECODER_MODE_RAM; else if (strcmp(ident, "volatile") == 0) return CXL_DECODER_MODE_RAM; else if (strcmp(ident, "pmem") == 0) return CXL_DECODER_MODE_PMEM; return CXL_DECODER_MODE_NONE; } enum cxl_decoder_mode cxl_decoder_get_mode(struct cxl_decoder *decoder); int cxl_decoder_set_mode(struct cxl_decoder *decoder, enum cxl_decoder_mode mode); int cxl_decoder_set_dpa_size(struct cxl_decoder *decoder, unsigned long long size); const char *cxl_decoder_get_devname(struct cxl_decoder *decoder); struct cxl_target *cxl_decoder_get_target_by_memdev(struct cxl_decoder *decoder, struct cxl_memdev *memdev); struct cxl_target * cxl_decoder_get_target_by_position(struct cxl_decoder *decoder, int position); int cxl_decoder_get_nr_targets(struct cxl_decoder *decoder); struct cxl_ctx *cxl_decoder_get_ctx(struct cxl_decoder *decoder); int cxl_decoder_get_id(struct cxl_decoder *decoder); struct cxl_port *cxl_decoder_get_port(struct cxl_decoder *decoder); enum cxl_decoder_target_type { CXL_DECODER_TTYPE_UNKNOWN, CXL_DECODER_TTYPE_EXPANDER, CXL_DECODER_TTYPE_ACCELERATOR, }; enum cxl_decoder_target_type cxl_decoder_get_target_type(struct cxl_decoder *decoder); bool cxl_decoder_is_pmem_capable(struct cxl_decoder *decoder); bool cxl_decoder_is_volatile_capable(struct cxl_decoder *decoder); bool cxl_decoder_is_mem_capable(struct cxl_decoder *decoder); bool cxl_decoder_is_accelmem_capable(struct cxl_decoder *decoder); bool cxl_decoder_is_locked(struct cxl_decoder *decoder); unsigned int cxl_decoder_get_interleave_granularity(struct cxl_decoder *decoder); unsigned int cxl_decoder_get_interleave_ways(struct cxl_decoder *decoder); struct cxl_region *cxl_decoder_get_region(struct cxl_decoder *decoder); struct cxl_region *cxl_decoder_create_pmem_region(struct cxl_decoder *decoder); struct cxl_region *cxl_decoder_create_ram_region(struct cxl_decoder *decoder); struct cxl_decoder *cxl_decoder_get_by_name(struct cxl_ctx *ctx, const char *ident); struct cxl_memdev *cxl_decoder_get_memdev(struct cxl_decoder *decoder); #define cxl_decoder_foreach(port, decoder) \ for (decoder = cxl_decoder_get_first(port); decoder != NULL; \ decoder = cxl_decoder_get_next(decoder)) #define cxl_decoder_foreach_reverse(port, decoder) \ for (decoder = cxl_decoder_get_last(port); decoder != NULL; \ decoder = cxl_decoder_get_prev(decoder)) struct cxl_target; struct cxl_target *cxl_target_get_first(struct cxl_decoder *decoder); struct cxl_target *cxl_target_get_next(struct cxl_target *target); struct cxl_decoder *cxl_target_get_decoder(struct cxl_target *target); int cxl_target_get_position(struct cxl_target *target); unsigned long cxl_target_get_id(struct cxl_target *target); const char *cxl_target_get_devname(struct cxl_target *target); bool cxl_target_maps_memdev(struct cxl_target *target, struct cxl_memdev *memdev); const char *cxl_target_get_physical_node(struct cxl_target *target); const char *cxl_target_get_firmware_node(struct cxl_target *target); #define cxl_target_foreach(decoder, target) \ for (target = cxl_target_get_first(decoder); target != NULL; \ target = cxl_target_get_next(target)) struct cxl_endpoint; struct cxl_endpoint *cxl_endpoint_get_first(struct cxl_port *parent); struct cxl_endpoint *cxl_endpoint_get_next(struct cxl_endpoint *endpoint); const char *cxl_endpoint_get_devname(struct cxl_endpoint *endpoint); int cxl_endpoint_get_id(struct cxl_endpoint *endpoint); struct cxl_ctx *cxl_endpoint_get_ctx(struct cxl_endpoint *endpoint); int cxl_endpoint_is_enabled(struct cxl_endpoint *endpoint); struct cxl_port *cxl_endpoint_get_parent(struct cxl_endpoint *endpoint); struct cxl_port *cxl_endpoint_get_port(struct cxl_endpoint *endpoint); const char *cxl_endpoint_get_host(struct cxl_endpoint *endpoint); struct cxl_bus *cxl_endpoint_get_bus(struct cxl_endpoint *endpoint); struct cxl_memdev *cxl_endpoint_get_memdev(struct cxl_endpoint *endpoint); int cxl_memdev_is_enabled(struct cxl_memdev *memdev); #define cxl_endpoint_foreach(port, endpoint) \ for (endpoint = cxl_endpoint_get_first(port); endpoint != NULL; \ endpoint = cxl_endpoint_get_next(endpoint)) struct cxl_region; struct cxl_region *cxl_region_get_first(struct cxl_decoder *decoder); struct cxl_region *cxl_region_get_next(struct cxl_region *region); int cxl_region_decode_is_committed(struct cxl_region *region); int cxl_region_is_enabled(struct cxl_region *region); int cxl_region_disable(struct cxl_region *region); int cxl_region_enable(struct cxl_region *region); int cxl_region_delete(struct cxl_region *region); struct cxl_ctx *cxl_region_get_ctx(struct cxl_region *region); struct cxl_decoder *cxl_region_get_decoder(struct cxl_region *region); int cxl_region_get_id(struct cxl_region *region); const char *cxl_region_get_devname(struct cxl_region *region); void cxl_region_get_uuid(struct cxl_region *region, uuid_t uu); unsigned long long cxl_region_get_size(struct cxl_region *region); unsigned long long cxl_region_get_resource(struct cxl_region *region); enum cxl_decoder_mode cxl_region_get_mode(struct cxl_region *region); unsigned int cxl_region_get_interleave_ways(struct cxl_region *region); unsigned int cxl_region_get_interleave_granularity(struct cxl_region *region); struct cxl_decoder *cxl_region_get_target_decoder(struct cxl_region *region, int position); struct daxctl_region *cxl_region_get_daxctl_region(struct cxl_region *region); int cxl_region_set_size(struct cxl_region *region, unsigned long long size); int cxl_region_set_uuid(struct cxl_region *region, uuid_t uu); int cxl_region_set_interleave_ways(struct cxl_region *region, unsigned int ways); int cxl_region_set_interleave_granularity(struct cxl_region *region, unsigned int granularity); int cxl_region_set_target(struct cxl_region *region, int position, struct cxl_decoder *decoder); int cxl_region_clear_target(struct cxl_region *region, int position); int cxl_region_clear_all_targets(struct cxl_region *region); int cxl_region_decode_commit(struct cxl_region *region); int cxl_region_decode_reset(struct cxl_region *region); bool cxl_region_qos_class_mismatch(struct cxl_region *region); #define cxl_region_foreach(decoder, region) \ for (region = cxl_region_get_first(decoder); region != NULL; \ region = cxl_region_get_next(region)) #define cxl_region_foreach_safe(decoder, region, _region) \ for (region = cxl_region_get_first(decoder), \ _region = region ? cxl_region_get_next(region) : NULL; \ region != NULL; \ region = _region, \ _region = _region ? cxl_region_get_next(_region) : NULL) struct cxl_memdev_mapping; struct cxl_memdev_mapping *cxl_mapping_get_first(struct cxl_region *region); struct cxl_memdev_mapping * cxl_mapping_get_next(struct cxl_memdev_mapping *mapping); struct cxl_decoder *cxl_mapping_get_decoder(struct cxl_memdev_mapping *mapping); unsigned int cxl_mapping_get_position(struct cxl_memdev_mapping *mapping); #define cxl_mapping_foreach(region, mapping) \ for (mapping = cxl_mapping_get_first(region); \ mapping != NULL; \ mapping = cxl_mapping_get_next(mapping)) struct cxl_cmd; const char *cxl_cmd_get_devname(struct cxl_cmd *cmd); struct cxl_cmd *cxl_cmd_new_raw(struct cxl_memdev *memdev, int opcode); int cxl_cmd_set_input_payload(struct cxl_cmd *cmd, void *in, int size); int cxl_cmd_set_output_payload(struct cxl_cmd *cmd, void *out, int size); void cxl_cmd_ref(struct cxl_cmd *cmd); void cxl_cmd_unref(struct cxl_cmd *cmd); int cxl_cmd_submit(struct cxl_cmd *cmd); int cxl_cmd_get_mbox_status(struct cxl_cmd *cmd); int cxl_cmd_get_out_size(struct cxl_cmd *cmd); struct cxl_cmd *cxl_cmd_new_identify(struct cxl_memdev *memdev); int cxl_cmd_identify_get_fw_rev(struct cxl_cmd *cmd, char *fw_rev, int fw_len); unsigned long long cxl_cmd_identify_get_total_size(struct cxl_cmd *cmd); unsigned long long cxl_cmd_identify_get_volatile_only_size(struct cxl_cmd *cmd); unsigned long long cxl_cmd_identify_get_persistent_only_size(struct cxl_cmd *cmd); unsigned long long cxl_cmd_identify_get_partition_align(struct cxl_cmd *cmd); unsigned int cxl_cmd_identify_get_label_size(struct cxl_cmd *cmd); struct cxl_cmd *cxl_cmd_new_get_health_info(struct cxl_memdev *memdev); int cxl_cmd_health_info_get_maintenance_needed(struct cxl_cmd *cmd); int cxl_cmd_health_info_get_performance_degraded(struct cxl_cmd *cmd); int cxl_cmd_health_info_get_hw_replacement_needed(struct cxl_cmd *cmd); int cxl_cmd_health_info_get_media_normal(struct cxl_cmd *cmd); int cxl_cmd_health_info_get_media_not_ready(struct cxl_cmd *cmd); int cxl_cmd_health_info_get_media_persistence_lost(struct cxl_cmd *cmd); int cxl_cmd_health_info_get_media_data_lost(struct cxl_cmd *cmd); int cxl_cmd_health_info_get_media_normal(struct cxl_cmd *cmd); int cxl_cmd_health_info_get_media_not_ready(struct cxl_cmd *cmd); int cxl_cmd_health_info_get_media_persistence_lost(struct cxl_cmd *cmd); int cxl_cmd_health_info_get_media_data_lost(struct cxl_cmd *cmd); int cxl_cmd_health_info_get_media_powerloss_persistence_loss(struct cxl_cmd *cmd); int cxl_cmd_health_info_get_media_shutdown_persistence_loss(struct cxl_cmd *cmd); int cxl_cmd_health_info_get_media_persistence_loss_imminent(struct cxl_cmd *cmd); int cxl_cmd_health_info_get_media_powerloss_data_loss(struct cxl_cmd *cmd); int cxl_cmd_health_info_get_media_shutdown_data_loss(struct cxl_cmd *cmd); int cxl_cmd_health_info_get_media_data_loss_imminent(struct cxl_cmd *cmd); int cxl_cmd_health_info_get_ext_life_used_normal(struct cxl_cmd *cmd); int cxl_cmd_health_info_get_ext_life_used_warning(struct cxl_cmd *cmd); int cxl_cmd_health_info_get_ext_life_used_critical(struct cxl_cmd *cmd); int cxl_cmd_health_info_get_ext_temperature_normal(struct cxl_cmd *cmd); int cxl_cmd_health_info_get_ext_temperature_warning(struct cxl_cmd *cmd); int cxl_cmd_health_info_get_ext_temperature_critical(struct cxl_cmd *cmd); int cxl_cmd_health_info_get_ext_corrected_volatile_normal(struct cxl_cmd *cmd); int cxl_cmd_health_info_get_ext_corrected_volatile_warning(struct cxl_cmd *cmd); int cxl_cmd_health_info_get_ext_corrected_persistent_normal(struct cxl_cmd *cmd); int cxl_cmd_health_info_get_ext_corrected_persistent_warning(struct cxl_cmd *cmd); int cxl_cmd_health_info_get_life_used(struct cxl_cmd *cmd); int cxl_cmd_health_info_get_temperature(struct cxl_cmd *cmd); int cxl_cmd_health_info_get_dirty_shutdowns(struct cxl_cmd *cmd); int cxl_cmd_health_info_get_volatile_errors(struct cxl_cmd *cmd); int cxl_cmd_health_info_get_pmem_errors(struct cxl_cmd *cmd); struct cxl_cmd *cxl_cmd_new_get_alert_config(struct cxl_memdev *memdev); int cxl_cmd_alert_config_life_used_prog_warn_threshold_valid( struct cxl_cmd *cmd); int cxl_cmd_alert_config_dev_over_temperature_prog_warn_threshold_valid( struct cxl_cmd *cmd); int cxl_cmd_alert_config_dev_under_temperature_prog_warn_threshold_valid( struct cxl_cmd *cmd); int cxl_cmd_alert_config_corrected_volatile_mem_err_prog_warn_threshold_valid( struct cxl_cmd *cmd); int cxl_cmd_alert_config_corrected_pmem_err_prog_warn_threshold_valid( struct cxl_cmd *cmd); int cxl_cmd_alert_config_life_used_prog_warn_threshold_writable( struct cxl_cmd *cmd); int cxl_cmd_alert_config_dev_over_temperature_prog_warn_threshold_writable( struct cxl_cmd *cmd); int cxl_cmd_alert_config_dev_under_temperature_prog_warn_threshold_writable( struct cxl_cmd *cmd); int cxl_cmd_alert_config_corrected_volatile_mem_err_prog_warn_threshold_writable( struct cxl_cmd *cmd); int cxl_cmd_alert_config_corrected_pmem_err_prog_warn_threshold_writable( struct cxl_cmd *cmd); int cxl_cmd_alert_config_get_life_used_crit_alert_threshold(struct cxl_cmd *cmd); int cxl_cmd_alert_config_get_life_used_prog_warn_threshold(struct cxl_cmd *cmd); int cxl_cmd_alert_config_get_dev_over_temperature_crit_alert_threshold( struct cxl_cmd *cmd); int cxl_cmd_alert_config_get_dev_under_temperature_crit_alert_threshold( struct cxl_cmd *cmd); int cxl_cmd_alert_config_get_dev_over_temperature_prog_warn_threshold( struct cxl_cmd *cmd); int cxl_cmd_alert_config_get_dev_under_temperature_prog_warn_threshold( struct cxl_cmd *cmd); int cxl_cmd_alert_config_get_corrected_volatile_mem_err_prog_warn_threshold( struct cxl_cmd *cmd); int cxl_cmd_alert_config_get_corrected_pmem_err_prog_warn_threshold( struct cxl_cmd *cmd); struct cxl_cmd *cxl_cmd_new_read_label(struct cxl_memdev *memdev, unsigned int offset, unsigned int length); ssize_t cxl_cmd_read_label_get_payload(struct cxl_cmd *cmd, void *buf, unsigned int length); struct cxl_cmd *cxl_cmd_new_write_label(struct cxl_memdev *memdev, void *buf, unsigned int offset, unsigned int length); struct cxl_cmd *cxl_cmd_new_get_partition(struct cxl_memdev *memdev); unsigned long long cxl_cmd_partition_get_active_volatile_size(struct cxl_cmd *cmd); unsigned long long cxl_cmd_partition_get_active_persistent_size(struct cxl_cmd *cmd); unsigned long long cxl_cmd_partition_get_next_volatile_size(struct cxl_cmd *cmd); unsigned long long cxl_cmd_partition_get_next_persistent_size(struct cxl_cmd *cmd); struct cxl_cmd *cxl_cmd_new_set_partition(struct cxl_memdev *memdev, unsigned long long volatile_size); enum cxl_setpartition_mode { CXL_SETPART_NEXTBOOT, CXL_SETPART_IMMEDIATE, }; int cxl_cmd_partition_set_mode(struct cxl_cmd *cmd, enum cxl_setpartition_mode mode); int cxl_memdev_trigger_poison_list(struct cxl_memdev *memdev); int cxl_region_trigger_poison_list(struct cxl_region *region); int cxl_cmd_alert_config_set_life_used_prog_warn_threshold(struct cxl_cmd *cmd, int threshold); int cxl_cmd_alert_config_set_dev_over_temperature_prog_warn_threshold( struct cxl_cmd *cmd, int threshold); int cxl_cmd_alert_config_set_dev_under_temperature_prog_warn_threshold( struct cxl_cmd *cmd, int threshold); int cxl_cmd_alert_config_set_corrected_volatile_mem_err_prog_warn_threshold( struct cxl_cmd *cmd, int threshold); int cxl_cmd_alert_config_set_corrected_pmem_err_prog_warn_threshold( struct cxl_cmd *cmd, int threshold); int cxl_cmd_alert_config_set_valid_alert_actions(struct cxl_cmd *cmd, int action); int cxl_cmd_alert_config_set_enable_alert_actions(struct cxl_cmd *cmd, int enable); struct cxl_cmd *cxl_cmd_new_set_alert_config(struct cxl_memdev *memdev); #ifdef __cplusplus } /* extern "C" */ #endif #endif ndctl-81/cxl/list.c000066400000000000000000000115011476737544500143410ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) 2020-2022 Intel Corporation. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include "filter.h" static struct cxl_filter_params param; static bool debug; static const struct option options[] = { OPT_STRING('m', "memdev", ¶m.memdev_filter, "memory device name(s)", "filter by CXL memory device name(s)"), OPT_STRING('s', "serial", ¶m.serial_filter, "memory device serial(s)", "filter by CXL memory device serial number(s)"), OPT_BOOLEAN('M', "memdevs", ¶m.memdevs, "include CXL memory device info"), OPT_STRING('b', "bus", ¶m.bus_filter, "bus device name", "filter by CXL bus device name(s)"), OPT_BOOLEAN('B', "buses", ¶m.buses, "include CXL bus info"), OPT_STRING('p', "port", ¶m.port_filter, "port device name", "filter by CXL port device name(s)"), OPT_BOOLEAN('P', "ports", ¶m.ports, "include CXL port info"), OPT_BOOLEAN('S', "single", ¶m.single, "skip listing descendant objects"), OPT_STRING('e', "endpoint", ¶m.endpoint_filter, "endpoint device name", "filter by CXL endpoint device name(s)"), OPT_BOOLEAN('E', "endpoints", ¶m.endpoints, "include CXL endpoint info"), OPT_STRING('d', "decoder", ¶m.decoder_filter, "decoder device name", "filter by CXL decoder device name(s) / class"), OPT_BOOLEAN('D', "decoders", ¶m.decoders, "include CXL decoder info"), OPT_BOOLEAN('T', "targets", ¶m.targets, "include CXL target data with decoders, ports, or regions"), OPT_STRING('r', "region", ¶m.region_filter, "region name", "filter by CXL region name(s)"), OPT_BOOLEAN('R', "regions", ¶m.regions, "include CXL regions"), OPT_BOOLEAN('X', "dax", ¶m.dax, "include CXL DAX region enumeration"), OPT_BOOLEAN('i', "idle", ¶m.idle, "include disabled devices"), OPT_BOOLEAN('u', "human", ¶m.human, "use human friendly number formats"), OPT_BOOLEAN('H', "health", ¶m.health, "include memory device health information"), OPT_BOOLEAN('I', "partition", ¶m.partition, "include memory device partition information"), OPT_BOOLEAN('F', "firmware", ¶m.fw, "include memory device firmware information"), OPT_BOOLEAN('A', "alert-config", ¶m.alert_config, "include alert configuration information"), OPT_BOOLEAN('L', "media-errors", ¶m.media_errors, "include media-error information "), OPT_INCR('v', "verbose", ¶m.verbose, "increase output detail"), #ifdef ENABLE_DEBUG OPT_BOOLEAN(0, "debug", &debug, "debug list walk"), #endif OPT_END(), }; static int num_list_flags(void) { return !!param.memdevs + !!param.buses + !!param.ports + !!param.endpoints + !!param.decoders + !!param.regions; } int cmd_list(int argc, const char **argv, struct cxl_ctx *ctx) { const char * const u[] = { "cxl list []", NULL }; struct json_object *jtopology; int i; argc = parse_options(argc, argv, options, u, 0); for (i = 0; i < argc; i++) error("unknown parameter \"%s\"\n", argv[i]); if (argc) usage_with_options(u, options); if (param.single && !param.port_filter) { error("-S/--single expects a port filter: -p/--port=\n"); usage_with_options(u, options); } if (num_list_flags() == 0) { if (param.memdev_filter || param.serial_filter) param.memdevs = true; if (param.bus_filter) param.buses = true; if (param.port_filter) param.ports = true; if (param.endpoint_filter) param.endpoints = true; if (param.decoder_filter) param.decoders = true; param.single = true; if (param.region_filter) param.regions = true; } /* List regions and memdevs by default */ if (num_list_flags() == 0) { param.regions = true; param.memdevs = true; } switch(param.verbose){ default: case 3: param.health = true; param.partition = true; param.fw = true; param.alert_config = true; param.dax = true; param.media_errors = true; /* fallthrough */ case 2: param.idle = true; /* fallthrough */ case 1: param.buses = true; param.ports = true; param.endpoints = true; param.decoders = true; param.targets = true; param.regions = true; /*fallthrough*/ case 0: break; } log_init(¶m.ctx, "cxl list", "CXL_LIST_LOG"); if (debug) { cxl_set_log_priority(ctx, LOG_DEBUG); param.ctx.log_priority = LOG_DEBUG; } if (cxl_filter_has(param.port_filter, "root") && param.ports) param.buses = true; if (cxl_filter_has(param.port_filter, "endpoint") && param.ports) param.endpoints = true; dbg(¶m, "walk topology\n"); jtopology = cxl_filter_walk(ctx, ¶m); if (!jtopology) return -ENOMEM; util_display_json_array(stdout, jtopology, cxl_filter_to_flags(¶m)); return 0; } ndctl-81/cxl/memdev.c000066400000000000000000001032211476737544500146440ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) 2020-2021 Intel Corporation. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "json.h" #include "filter.h" struct action_context { FILE *f_out; FILE *f_in; struct json_object *jdevs; }; static struct parameters { const char *bus; const char *outfile; const char *infile; const char *fw_file; unsigned len; unsigned offset; bool verbose; bool serial; bool force; bool align; bool cancel; bool wait; const char *type; const char *size; const char *decoder_filter; const char *life_used_threshold; const char *dev_over_temperature_threshold; const char *dev_under_temperature_threshold; const char *corrected_volatile_mem_err_threshold; const char *corrected_pmem_err_threshold; const char *life_used_alert; const char *dev_over_temperature_alert; const char *dev_under_temperature_alert; const char *corrected_volatile_mem_err_alert; const char *corrected_pmem_err_alert; int timeout; } param = { .timeout = -1, }; static struct log_ctx ml; struct alert_context { int valid_alert_actions; int enable_alert_actions; int life_used_threshold; int dev_over_temperature_threshold; int dev_under_temperature_threshold; int corrected_volatile_mem_err_threshold; int corrected_pmem_err_threshold; }; enum cxl_setalert_event { CXL_SETALERT_LIFE_USED, CXL_SETALERT_OVER_TEMP, CXL_SETALERT_UNDER_TEMP, CXL_SETALERT_VOLATILE_MEM_ERROR, CXL_SETALERT_PMEM_ERROR, }; enum cxl_setpart_type { CXL_SETPART_PMEM, CXL_SETPART_VOLATILE, }; #define BASE_OPTIONS() \ OPT_STRING('b', "bus", ¶m.bus, "bus name", \ "Limit operation to the specified bus"), \ OPT_BOOLEAN('v',"verbose", ¶m.verbose, "turn on debug"), \ OPT_BOOLEAN('S', "serial", ¶m.serial, "use serial numbers to id memdevs") #define READ_OPTIONS() \ OPT_STRING('o', "output", ¶m.outfile, "output-file", \ "filename to write label area contents") #define WRITE_OPTIONS() \ OPT_STRING('i', "input", ¶m.infile, "input-file", \ "filename to read label area data") #define LABEL_OPTIONS() \ OPT_UINTEGER('s', "size", ¶m.len, "number of label bytes to operate"), \ OPT_UINTEGER('O', "offset", ¶m.offset, \ "offset into the label area to start operation") #define DISABLE_OPTIONS() \ OPT_BOOLEAN('f', "force", ¶m.force, \ "DANGEROUS: override active memdev safety checks") #define SET_PARTITION_OPTIONS() \ OPT_STRING('t', "type", ¶m.type, "type", \ "'pmem' or 'ram' (volatile) (Default: 'pmem')"), \ OPT_STRING('s', "size", ¶m.size, "size", \ "size in bytes (Default: all available capacity)"), \ OPT_BOOLEAN('a', "align", ¶m.align, \ "auto-align --size per device's requirement") #define RESERVE_DPA_OPTIONS() \ OPT_STRING('s', "size", ¶m.size, "size", \ "size in bytes (Default: all available capacity)") #define DPA_OPTIONS() \ OPT_STRING('d', "decoder", ¶m.decoder_filter, \ "decoder instance id", \ "override the automatic decoder selection"), \ OPT_STRING('t', "type", ¶m.type, "type", \ "'pmem' or 'ram' (volatile) (Default: 'pmem')"), \ OPT_BOOLEAN('f', "force", ¶m.force, \ "Attempt 'expected to fail' operations") #define FW_OPTIONS() \ OPT_STRING('F', "firmware-file", ¶m.fw_file, "firmware-file", \ "firmware image file to use for the update"), \ OPT_BOOLEAN('c', "cancel", ¶m.cancel, \ "attempt to abort an in-progress firmware update"), \ OPT_BOOLEAN('w', "wait", ¶m.wait, \ "wait for firmware update to complete before returning") #define SET_ALERT_OPTIONS() \ OPT_STRING('L', "life-used-threshold", ¶m.life_used_threshold, \ "threshold", "threshold value for life used warning alert"), \ OPT_STRING('\0', "life-used-alert", ¶m.life_used_alert, \ "'on' or 'off'", "enable or disable life used warning alert"), \ OPT_STRING('O', "over-temperature-threshold", \ ¶m.dev_over_temperature_threshold, "threshold", \ "threshold value for device over temperature warning alert"), \ OPT_STRING('\0', "over-temperature-alert", \ ¶m.dev_over_temperature_alert, "'on' or 'off'", \ "enable or disable device over temperature warning alert"), \ OPT_STRING('U', "under-temperature-threshold", \ ¶m.dev_under_temperature_threshold, "threshold", \ "threshold value for device under temperature warning alert"), \ OPT_STRING('\0', "under-temperature-alert", \ ¶m.dev_under_temperature_alert, "'on' or 'off'", \ "enable or disable device under temperature warning alert"), \ OPT_STRING('V', "volatile-mem-err-threshold", \ ¶m.corrected_volatile_mem_err_threshold, "threshold", \ "threshold value for corrected volatile mem error warning alert"), \ OPT_STRING('\0', "volatile-mem-err-alert", \ ¶m.corrected_volatile_mem_err_alert, "'on' or 'off'", \ "enable or disable corrected volatile mem error warning alert"), \ OPT_STRING('P', "pmem-err-threshold", \ ¶m.corrected_pmem_err_threshold, "threshold", \ "threshold value for corrected pmem error warning alert"), \ OPT_STRING('\0', "pmem-err-alert", \ ¶m.corrected_pmem_err_alert, "'on' or 'off'", \ "enable or disable corrected pmem error warning alert") #define WAIT_SANITIZE_OPTIONS() \ OPT_INTEGER('t', "timeout", ¶m.timeout, \ "time in milliseconds to wait for overwrite completion (default: infinite)") static const struct option read_options[] = { BASE_OPTIONS(), LABEL_OPTIONS(), READ_OPTIONS(), OPT_END(), }; static const struct option write_options[] = { BASE_OPTIONS(), LABEL_OPTIONS(), WRITE_OPTIONS(), OPT_END(), }; static const struct option zero_options[] = { BASE_OPTIONS(), LABEL_OPTIONS(), OPT_END(), }; static const struct option disable_options[] = { BASE_OPTIONS(), DISABLE_OPTIONS(), OPT_END(), }; static const struct option enable_options[] = { BASE_OPTIONS(), OPT_END(), }; static const struct option set_partition_options[] = { BASE_OPTIONS(), SET_PARTITION_OPTIONS(), OPT_END(), }; static const struct option reserve_dpa_options[] = { BASE_OPTIONS(), RESERVE_DPA_OPTIONS(), DPA_OPTIONS(), OPT_END(), }; static const struct option free_dpa_options[] = { BASE_OPTIONS(), DPA_OPTIONS(), OPT_END(), }; static const struct option update_fw_options[] = { BASE_OPTIONS(), FW_OPTIONS(), OPT_END(), }; static const struct option set_alert_options[] = { BASE_OPTIONS(), SET_ALERT_OPTIONS(), OPT_END(), }; static const struct option wait_sanitize_options[] = { BASE_OPTIONS(), WAIT_SANITIZE_OPTIONS(), OPT_END(), }; enum reserve_dpa_mode { DPA_ALLOC, DPA_FREE, }; static int __reserve_dpa(struct cxl_memdev *memdev, enum reserve_dpa_mode alloc_mode, struct action_context *actx) { struct cxl_decoder *decoder, *auto_target = NULL, *target = NULL; struct cxl_endpoint *endpoint = cxl_memdev_get_endpoint(memdev); const char *devname = cxl_memdev_get_devname(memdev); unsigned long long avail_dpa, size; enum cxl_decoder_mode mode; struct cxl_port *port; char buf[256]; int rc; if (param.type) { mode = cxl_decoder_mode_from_ident(param.type); if (mode == CXL_DECODER_MODE_NONE) { log_err(&ml, "%s: unsupported type: %s\n", devname, param.type); return -EINVAL; } } else mode = CXL_DECODER_MODE_RAM; if (!endpoint) { log_err(&ml, "%s: CXL operation disabled\n", devname); return -ENXIO; } port = cxl_endpoint_get_port(endpoint); if (mode == CXL_DECODER_MODE_RAM) avail_dpa = cxl_memdev_get_ram_size(memdev); else avail_dpa = cxl_memdev_get_pmem_size(memdev); cxl_decoder_foreach(port, decoder) { size = cxl_decoder_get_dpa_size(decoder); if (size == ULLONG_MAX) continue; if (cxl_decoder_get_mode(decoder) != mode) continue; if (size > avail_dpa) { log_err(&ml, "%s: capacity accounting error\n", devname); return -ENXIO; } avail_dpa -= size; } if (!param.size) if (alloc_mode == DPA_ALLOC) { size = avail_dpa; if (!avail_dpa) { log_err(&ml, "%s: no available capacity\n", devname); return -ENOSPC; } } else size = 0; else { size = parse_size64(param.size); if (size == ULLONG_MAX) { log_err(&ml, "%s: failed to parse size option '%s'\n", devname, param.size); return -EINVAL; } if (size > avail_dpa) { log_err(&ml, "%s: '%s' exceeds available capacity\n", devname, param.size); if (!param.force) return -ENOSPC; } } /* * Find next free decoder, assumes cxl_decoder_foreach() is in * hardware instance-id order */ if (alloc_mode == DPA_ALLOC) cxl_decoder_foreach(port, decoder) { /* first 0-dpa_size is our target */ if (cxl_decoder_get_dpa_size(decoder) == 0) { auto_target = decoder; break; } } else cxl_decoder_foreach_reverse(port, decoder) { /* nothing to free? */ if (!cxl_decoder_get_dpa_size(decoder)) continue; /* * Active decoders can't be freed, and by definition all * previous decoders must also be active */ if (cxl_decoder_get_size(decoder)) break; /* first dpa_size > 0 + disabled decoder is our target */ if (cxl_decoder_get_dpa_size(decoder) < ULLONG_MAX) { auto_target = decoder; break; } } if (param.decoder_filter) { unsigned long id; char *end; id = strtoul(param.decoder_filter, &end, 0); /* allow for standalone ordinal decoder ids */ if (*end == '\0') rc = snprintf(buf, sizeof(buf), "decoder%d.%ld", cxl_port_get_id(port), id); else rc = snprintf(buf, sizeof(buf), "%s", param.decoder_filter); if (rc >= (int)sizeof(buf)) { log_err(&ml, "%s: decoder filter '%s' too long\n", devname, param.decoder_filter); return -EINVAL; } if (alloc_mode == DPA_ALLOC) cxl_decoder_foreach(port, decoder) { target = util_cxl_decoder_filter(decoder, buf); if (target) break; } else cxl_decoder_foreach_reverse(port, decoder) { target = util_cxl_decoder_filter(decoder, buf); if (target) break; } if (!target) { log_err(&ml, "%s: no match for decoder: '%s'\n", devname, param.decoder_filter); return -ENXIO; } if (target != auto_target) { log_err(&ml, "%s: %s is out of sequence\n", devname, cxl_decoder_get_devname(target)); if (!param.force) return -EINVAL; } } if (!target) target = auto_target; if (!target) { log_err(&ml, "%s: no suitable decoder found\n", devname); return -ENXIO; } if (cxl_decoder_get_mode(target) != mode) { rc = cxl_decoder_set_dpa_size(target, 0); if (rc) { log_err(&ml, "%s: %s: failed to clear allocation to set mode\n", devname, cxl_decoder_get_devname(target)); return rc; } rc = cxl_decoder_set_mode(target, mode); if (rc) { log_err(&ml, "%s: %s: failed to set %s mode\n", devname, cxl_decoder_get_devname(target), mode == CXL_DECODER_MODE_PMEM ? "pmem" : "ram"); return rc; } } rc = cxl_decoder_set_dpa_size(target, size); if (rc) log_err(&ml, "%s: %s: failed to set dpa allocation\n", devname, cxl_decoder_get_devname(target)); else { struct json_object *jdev, *jdecoder; unsigned long flags = 0; if (actx->f_out == stdout && isatty(1)) flags |= UTIL_JSON_HUMAN; jdev = util_cxl_memdev_to_json(memdev, flags); jdecoder = util_cxl_decoder_to_json(target, flags); if (!jdev || !jdecoder) { json_object_put(jdev); json_object_put(jdecoder); } else { json_object_object_add(jdev, "decoder", jdecoder); json_object_array_add(actx->jdevs, jdev); } } return rc; } static int action_reserve_dpa(struct cxl_memdev *memdev, struct action_context *actx) { return __reserve_dpa(memdev, DPA_ALLOC, actx); } static int action_free_dpa(struct cxl_memdev *memdev, struct action_context *actx) { return __reserve_dpa(memdev, DPA_FREE, actx); } static int action_disable(struct cxl_memdev *memdev, struct action_context *actx) { struct cxl_endpoint *ep; struct cxl_port *port; if (!cxl_memdev_is_enabled(memdev)) return 0; ep = cxl_memdev_get_endpoint(memdev); if (!ep) return -ENODEV; port = cxl_endpoint_get_port(ep); if (!port) return -ENODEV; if (cxl_port_decoders_committed(port)) { log_err(&ml, "%s is part of an active region\n", cxl_memdev_get_devname(memdev)); if (!param.force) return -EBUSY; log_err(&ml, "Forcing %s disable with an active region!\n", cxl_memdev_get_devname(memdev)); } return cxl_memdev_disable_invalidate(memdev); } static int action_enable(struct cxl_memdev *memdev, struct action_context *actx) { return cxl_memdev_enable(memdev); } static int action_zero(struct cxl_memdev *memdev, struct action_context *actx) { size_t size; int rc; if (param.len) { size = param.len; } else { size = cxl_memdev_get_label_size(memdev); if (size == SIZE_MAX) return -EINVAL; } if (cxl_memdev_nvdimm_bridge_active(memdev)) { log_err(&ml, "%s: has active nvdimm bridge, abort label write\n", cxl_memdev_get_devname(memdev)); return -EBUSY; } rc = cxl_memdev_zero_label(memdev, size, param.offset); if (rc < 0) log_err(&ml, "%s: label zeroing failed: %s\n", cxl_memdev_get_devname(memdev), strerror(-rc)); return rc; } static int action_write(struct cxl_memdev *memdev, struct action_context *actx) { size_t size = param.len, read_len; unsigned char *buf; int rc; if (cxl_memdev_nvdimm_bridge_active(memdev)) { log_err(&ml, "%s: has active nvdimm bridge, abort label write\n", cxl_memdev_get_devname(memdev)); return -EBUSY; } if (!size) { size_t label_size = cxl_memdev_get_label_size(memdev); if (label_size == SIZE_MAX) return -EINVAL; fseek(actx->f_in, 0L, SEEK_END); size = ftell(actx->f_in); fseek(actx->f_in, 0L, SEEK_SET); if (size > label_size) { log_err(&ml, "File size (%zu) greater than label area size (%zu), aborting\n", size, label_size); return -EINVAL; } } buf = calloc(1, size); if (!buf) return -ENOMEM; read_len = fread(buf, 1, size, actx->f_in); if (read_len != size) { rc = -ENXIO; goto out; } rc = cxl_memdev_write_label(memdev, buf, size, param.offset); if (rc < 0) log_err(&ml, "%s: label write failed: %s\n", cxl_memdev_get_devname(memdev), strerror(-rc)); out: free(buf); return rc; } static int action_read(struct cxl_memdev *memdev, struct action_context *actx) { size_t size, write_len; char *buf; int rc; if (param.len) { size = param.len; } else { size = cxl_memdev_get_label_size(memdev); if (size == SIZE_MAX) return -EINVAL; } buf = calloc(1, size); if (!buf) return -ENOMEM; rc = cxl_memdev_read_label(memdev, buf, size, param.offset); if (rc < 0) { log_err(&ml, "%s: label read failed: %s\n", cxl_memdev_get_devname(memdev), strerror(-rc)); goto out; } write_len = fwrite(buf, 1, size, actx->f_out); if (write_len != size) { rc = -ENXIO; goto out; } fflush(actx->f_out); out: free(buf); return rc; } static unsigned long long partition_align(const char *devname, enum cxl_setpart_type type, unsigned long long volatile_size, unsigned long long alignment, unsigned long long available) { if (IS_ALIGNED(volatile_size, alignment)) return volatile_size; if (!param.align) { log_err(&ml, "%s: size %lld is not partition aligned %lld\n", devname, volatile_size, alignment); return ULLONG_MAX; } /* Align based on partition type to fulfill users size request */ if (type == CXL_SETPART_PMEM) volatile_size = ALIGN_DOWN(volatile_size, alignment); else volatile_size = ALIGN(volatile_size, alignment); /* Fail if the align pushes size over the available limit. */ if (volatile_size > available) { log_err(&ml, "%s: aligned partition size %lld exceeds available size %lld\n", devname, volatile_size, available); volatile_size = ULLONG_MAX; } return volatile_size; } static unsigned long long param_size_to_volatile_size(const char *devname, enum cxl_setpart_type type, unsigned long long size, unsigned long long available) { /* User omits size option. Apply all available capacity to type. */ if (size == ULLONG_MAX) { if (type == CXL_SETPART_PMEM) return 0; return available; } /* User includes a size option. Apply it to type */ if (size > available) { log_err(&ml, "%s: %lld exceeds available capacity %lld\n", devname, size, available); return ULLONG_MAX; } if (type == CXL_SETPART_PMEM) return available - size; return size; } /* * Return the volatile_size to use in the CXL set paritition * command, or ULLONG_MAX if unable to validate the partition * request. */ static unsigned long long validate_partition(struct cxl_memdev *memdev, enum cxl_setpart_type type, unsigned long long size) { unsigned long long total_cap, volatile_only, persistent_only; const char *devname = cxl_memdev_get_devname(memdev); unsigned long long volatile_size = ULLONG_MAX; unsigned long long available, alignment; struct cxl_cmd *cmd; int rc; cmd = cxl_cmd_new_identify(memdev); if (!cmd) return ULLONG_MAX; rc = cxl_cmd_submit(cmd); if (rc < 0) goto out; rc = cxl_cmd_get_mbox_status(cmd); if (rc != 0) goto out; alignment = cxl_cmd_identify_get_partition_align(cmd); if (alignment == 0) { log_err(&ml, "%s: no available capacity\n", devname); goto out; } /* Calculate the actual available capacity */ total_cap = cxl_cmd_identify_get_total_size(cmd); volatile_only = cxl_cmd_identify_get_volatile_only_size(cmd); persistent_only = cxl_cmd_identify_get_persistent_only_size(cmd); available = total_cap - volatile_only - persistent_only; /* Translate the users size request into an aligned volatile_size */ volatile_size = param_size_to_volatile_size(devname, type, size, available); if (volatile_size == ULLONG_MAX) goto out; volatile_size = partition_align(devname, type, volatile_size, alignment, available); out: cxl_cmd_unref(cmd); return volatile_size; } static int action_setpartition(struct cxl_memdev *memdev, struct action_context *actx) { const char *devname = cxl_memdev_get_devname(memdev); enum cxl_setpart_type type = CXL_SETPART_PMEM; unsigned long long size = ULLONG_MAX; struct json_object *jmemdev; unsigned long flags; struct cxl_cmd *cmd; int rc; if (param.type) { if (strcmp(param.type, "pmem") == 0) /* default */; else if (strcmp(param.type, "volatile") == 0) type = CXL_SETPART_VOLATILE; else if (strcmp(param.type, "ram") == 0) type = CXL_SETPART_VOLATILE; else { log_err(&ml, "invalid type '%s'\n", param.type); return -EINVAL; } } if (param.size) { size = parse_size64(param.size); if (size == ULLONG_MAX) { log_err(&ml, "%s: failed to parse size option '%s'\n", devname, param.size); return -EINVAL; } } size = validate_partition(memdev, type, size); if (size == ULLONG_MAX) return -EINVAL; cmd = cxl_cmd_new_set_partition(memdev, size); if (!cmd) { rc = -ENXIO; goto out_err; } rc = cxl_cmd_submit(cmd); if (rc < 0) { log_err(&ml, "cmd submission failed: %s\n", strerror(-rc)); goto out_cmd; } rc = cxl_cmd_get_mbox_status(cmd); if (rc != 0) { log_err(&ml, "%s: mbox status: %d\n", __func__, rc); rc = -ENXIO; } out_cmd: cxl_cmd_unref(cmd); out_err: if (rc) log_err(&ml, "%s error: %s\n", devname, strerror(-rc)); flags = UTIL_JSON_PARTITION; if (actx->f_out == stdout && isatty(1)) flags |= UTIL_JSON_HUMAN; jmemdev = util_cxl_memdev_to_json(memdev, flags); if (actx->jdevs && jmemdev) json_object_array_add(actx->jdevs, jmemdev); return rc; } static int action_wait_sanitize(struct cxl_memdev *memdev, struct action_context *actx) { return cxl_memdev_wait_sanitize(memdev, param.timeout); } static int action_update_fw(struct cxl_memdev *memdev, struct action_context *actx) { const char *devname = cxl_memdev_get_devname(memdev); struct json_object *jmemdev; unsigned long flags; int rc = 0; if (param.cancel) return cxl_memdev_cancel_fw_update(memdev); if (param.fw_file) { rc = cxl_memdev_update_fw(memdev, param.fw_file); if (rc) log_err(&ml, "%s error: %s\n", devname, strerror(-rc)); } if (param.wait) { while (cxl_memdev_fw_update_in_progress(memdev) || cxl_memdev_fw_update_get_remaining(memdev) > 0) sleep(1); } flags = UTIL_JSON_FIRMWARE; if (actx->f_out == stdout && isatty(1)) flags |= UTIL_JSON_HUMAN; jmemdev = util_cxl_memdev_to_json(memdev, flags); if (actx->jdevs && jmemdev) json_object_array_add(actx->jdevs, jmemdev); return rc; } static int validate_alert_threshold(enum cxl_setalert_event event, int threshold) { if (event == CXL_SETALERT_LIFE_USED) { if (threshold < 0 || threshold > 100) { log_err(&ml, "Invalid life used threshold: %d\n", threshold); return -EINVAL; } } else if (event == CXL_SETALERT_OVER_TEMP || event == CXL_SETALERT_UNDER_TEMP) { if (threshold < SHRT_MIN || threshold > SHRT_MAX) { log_err(&ml, "Invalid device temperature threshold: %d\n", threshold); return -EINVAL; } } else { if (threshold < 0 || threshold > USHRT_MAX) { log_err(&ml, "Invalid corrected mem error threshold: %d\n", threshold); return -EINVAL; } } return 0; } #define alert_param_set_threshold(arg, alert_event) \ { \ if (!param.arg##_alert) { \ if (param.arg##_threshold) { \ log_err(&ml, "Action not specified\n"); \ return -EINVAL; \ } \ } else if (strcmp(param.arg##_alert, "on") == 0) { \ if (param.arg##_threshold) { \ char *endptr; \ alertctx.arg##_threshold = \ strtol(param.arg##_threshold, &endptr, 10); \ if (endptr[0] != '\0') { \ log_err(&ml, "Invalid threshold: %s\n", \ param.arg##_threshold); \ return -EINVAL; \ } \ rc = validate_alert_threshold( \ alert_event, alertctx.arg##_threshold); \ if (rc != 0) \ return rc; \ alertctx.valid_alert_actions |= 1 << alert_event; \ alertctx.enable_alert_actions |= 1 << alert_event; \ } else { \ log_err(&ml, "Threshold not specified\n"); \ return -EINVAL; \ } \ } else if (strcmp(param.arg##_alert, "off") == 0) { \ if (!param.arg##_threshold) { \ alertctx.valid_alert_actions |= 1 << alert_event; \ alertctx.enable_alert_actions &= ~(1 << alert_event); \ } else { \ log_err(&ml, "Disable not require threshold\n"); \ return -EINVAL; \ } \ } else { \ log_err(&ml, "Invalid action: %s\n", param.arg##_alert); \ return -EINVAL; \ } \ } #define setup_threshold_field(arg) \ { \ if (param.arg##_threshold) \ cxl_cmd_alert_config_set_##arg##_prog_warn_threshold( \ cmd, alertctx.arg##_threshold); \ } static int action_set_alert_config(struct cxl_memdev *memdev, struct action_context *actx) { const char *devname = cxl_memdev_get_devname(memdev); struct cxl_cmd *cmd; struct alert_context alertctx = { 0 }; struct json_object *jmemdev; unsigned long flags; int rc = 0; alert_param_set_threshold(life_used, CXL_SETALERT_LIFE_USED) alert_param_set_threshold(dev_over_temperature, CXL_SETALERT_OVER_TEMP) alert_param_set_threshold(dev_under_temperature, CXL_SETALERT_UNDER_TEMP) alert_param_set_threshold(corrected_volatile_mem_err, CXL_SETALERT_VOLATILE_MEM_ERROR) alert_param_set_threshold(corrected_pmem_err, CXL_SETALERT_PMEM_ERROR) if (alertctx.valid_alert_actions == 0) { log_err(&ml, "No action specified\n"); return -EINVAL; } cmd = cxl_cmd_new_set_alert_config(memdev); if (!cmd) { rc = -ENXIO; goto out_err; } setup_threshold_field(life_used) setup_threshold_field(dev_over_temperature) setup_threshold_field(dev_under_temperature) setup_threshold_field(corrected_volatile_mem_err) setup_threshold_field(corrected_pmem_err) cxl_cmd_alert_config_set_valid_alert_actions( cmd, alertctx.valid_alert_actions); cxl_cmd_alert_config_set_enable_alert_actions( cmd, alertctx.enable_alert_actions); rc = cxl_cmd_submit(cmd); if (rc < 0) { log_err(&ml, "cmd submission failed: %s\n", strerror(-rc)); goto out_cmd; } rc = cxl_cmd_get_mbox_status(cmd); if (rc != 0) { log_err(&ml, "%s: mbox status: %d\n", __func__, rc); rc = -ENXIO; } out_cmd: cxl_cmd_unref(cmd); out_err: if (rc) log_err(&ml, "%s error: %s\n", devname, strerror(-rc)); flags = UTIL_JSON_ALERT_CONFIG; if (actx->f_out == stdout && isatty(1)) flags |= UTIL_JSON_HUMAN; jmemdev = util_cxl_memdev_to_json(memdev, flags); if (actx->jdevs && jmemdev) json_object_array_add(actx->jdevs, jmemdev); return rc; } static int memdev_action(int argc, const char **argv, struct cxl_ctx *ctx, int (*action)(struct cxl_memdev *memdev, struct action_context *actx), const struct option *options, const char *usage) { struct cxl_memdev *memdev, *single = NULL; struct action_context actx = { 0 }; int i, rc = 0, count = 0, err = 0; const char * const u[] = { usage, NULL }; unsigned long id; log_init(&ml, "cxl memdev", "CXL_MEMDEV_LOG"); argc = parse_options(argc, argv, options, u, 0); if (argc == 0) usage_with_options(u, options); for (i = 0; i < argc; i++) { if (param.serial) { char *end; strtoull(argv[i], &end, 0); if (end[0] == 0) continue; } else { if (strcmp(argv[i], "all") == 0) { argc = 1; break; } if (sscanf(argv[i], "mem%lu", &id) == 1) continue; if (sscanf(argv[i], "%lu", &id) == 1) continue; } log_err(&ml, "'%s' is not a valid memdev %s\n", argv[i], param.serial ? "serial number" : "name"); err++; } if (action == action_setpartition || action == action_reserve_dpa || action == action_free_dpa || action == action_update_fw || action == action_set_alert_config) actx.jdevs = json_object_new_array(); if (err == argc) { usage_with_options(u, options); return -EINVAL; } if (!param.outfile) actx.f_out = stdout; else { actx.f_out = fopen(param.outfile, "w+"); if (!actx.f_out) { log_err(&ml, "failed to open: %s: (%s)\n", param.outfile, strerror(errno)); rc = -errno; goto out; } } if (!param.infile) { actx.f_in = stdin; } else { actx.f_in = fopen(param.infile, "r"); if (!actx.f_in) { log_err(&ml, "failed to open: %s: (%s)\n", param.infile, strerror(errno)); rc = -errno; goto out_close_fout; } } if (param.verbose) { cxl_set_log_priority(ctx, LOG_DEBUG); ml.log_priority = LOG_DEBUG; } else ml.log_priority = LOG_INFO; rc = 0; err = 0; count = 0; for (i = 0; i < argc; i++) { bool found = false; cxl_memdev_foreach(ctx, memdev) { const char *memdev_filter = NULL; const char *serial_filter = NULL; if (param.serial) serial_filter = argv[i]; else memdev_filter = argv[i]; if (!util_cxl_memdev_filter(memdev, memdev_filter, serial_filter)) continue; if (!util_cxl_memdev_filter_by_bus(memdev, param.bus)) continue; found = true; if (action == action_write) { single = memdev; rc = 0; } else rc = action(memdev, &actx); if (rc == 0) count++; else if (rc && !err) err = rc; } if (!found) log_info(&ml, "no memdev matches %s\n", argv[i]); } rc = err; if (action == action_write) { if (count > 1) { error("write-labels only supports writing a single memdev\n"); usage_with_options(u, options); return -EINVAL; } else if (single) { rc = action(single, &actx); if (rc) count = 0; } } if (actx.f_in != stdin) fclose(actx.f_in); if (actx.jdevs) { unsigned long flags = 0; if (actx.f_out == stdout && isatty(1)) flags |= UTIL_JSON_HUMAN; util_display_json_array(actx.f_out, actx.jdevs, flags); } out_close_fout: if (actx.f_out != stdout) fclose(actx.f_out); out: /* * count if some actions succeeded, 0 if none were attempted, * negative error code otherwise. */ if (count > 0) return count; return rc; } int cmd_write_labels(int argc, const char **argv, struct cxl_ctx *ctx) { int count = memdev_action(argc, argv, ctx, action_write, write_options, "cxl write-labels [-i ]"); log_info(&ml, "wrote %d mem%s\n", count >= 0 ? count : 0, count > 1 ? "s" : ""); return count >= 0 ? 0 : EXIT_FAILURE; } int cmd_read_labels(int argc, const char **argv, struct cxl_ctx *ctx) { int count = memdev_action(argc, argv, ctx, action_read, read_options, "cxl read-labels [..] [-o ]"); log_info(&ml, "read %d mem%s\n", count >= 0 ? count : 0, count > 1 ? "s" : ""); return count >= 0 ? 0 : EXIT_FAILURE; } int cmd_zero_labels(int argc, const char **argv, struct cxl_ctx *ctx) { int count = memdev_action(argc, argv, ctx, action_zero, zero_options, "cxl zero-labels [..] []"); log_info(&ml, "zeroed %d mem%s\n", count >= 0 ? count : 0, count > 1 ? "s" : ""); return count >= 0 ? 0 : EXIT_FAILURE; } int cmd_disable_memdev(int argc, const char **argv, struct cxl_ctx *ctx) { int count = memdev_action( argc, argv, ctx, action_disable, disable_options, "cxl disable-memdev [..] []"); log_info(&ml, "disabled %d mem%s\n", count >= 0 ? count : 0, count > 1 ? "s" : ""); return count >= 0 ? 0 : EXIT_FAILURE; } int cmd_enable_memdev(int argc, const char **argv, struct cxl_ctx *ctx) { int count = memdev_action( argc, argv, ctx, action_enable, enable_options, "cxl enable-memdev [..] []"); log_info(&ml, "enabled %d mem%s\n", count >= 0 ? count : 0, count > 1 ? "s" : ""); return count >= 0 ? 0 : EXIT_FAILURE; } int cmd_set_partition(int argc, const char **argv, struct cxl_ctx *ctx) { int count = memdev_action(argc, argv, ctx, action_setpartition, set_partition_options, "cxl set-partition [..] []"); log_info(&ml, "set_partition %d mem%s\n", count >= 0 ? count : 0, count > 1 ? "s" : ""); return count >= 0 ? 0 : EXIT_FAILURE; } int cmd_reserve_dpa(int argc, const char **argv, struct cxl_ctx *ctx) { int count = memdev_action( argc, argv, ctx, action_reserve_dpa, reserve_dpa_options, "cxl reserve-dpa [..] []"); log_info(&ml, "reservation completed on %d mem device%s\n", count >= 0 ? count : 0, count > 1 ? "s" : ""); return count >= 0 ? 0 : EXIT_FAILURE; } int cmd_free_dpa(int argc, const char **argv, struct cxl_ctx *ctx) { int count = memdev_action( argc, argv, ctx, action_free_dpa, free_dpa_options, "cxl free-dpa [..] []"); log_info(&ml, "reservation release completed on %d mem device%s\n", count >= 0 ? count : 0, count > 1 ? "s" : ""); return count >= 0 ? 0 : EXIT_FAILURE; } int cmd_update_fw(int argc, const char **argv, struct cxl_ctx *ctx) { int count = memdev_action( argc, argv, ctx, action_update_fw, update_fw_options, "cxl update-firmware [..] []"); const char *op_string; if (param.cancel) op_string = "cancelled"; else if (param.wait) op_string = "completed"; else op_string = "started"; log_info(&ml, "firmware update %s on %d mem device%s\n", op_string, count >= 0 ? count : 0, count > 1 ? "s" : ""); return count >= 0 ? 0 : EXIT_FAILURE; } int cmd_set_alert_config(int argc, const char **argv, struct cxl_ctx *ctx) { int count = memdev_action( argc, argv, ctx, action_set_alert_config, set_alert_options, "cxl set-alert-config [..] []"); log_info(&ml, "set alert configuration for %d mem%s\n", count >= 0 ? count : 0, count > 1 ? "s" : ""); return count >= 0 ? 0 : EXIT_FAILURE; } int cmd_wait_sanitize(int argc, const char **argv, struct cxl_ctx *ctx) { int count = memdev_action( argc, argv, ctx, action_wait_sanitize, wait_sanitize_options, "cxl wait-sanitize [..] []"); log_info(&ml, "wait sanitize %s on %d mem device%s\n", count >= 0 ? "completed" : "failed", count >= 0 ? count : 0, count > 1 ? "s" : ""); return count >= 0 ? 0 : EXIT_FAILURE; } ndctl-81/cxl/meson.build000066400000000000000000000012771476737544500153750ustar00rootroot00000000000000cxl_src = [ 'cxl.c', 'list.c', 'port.c', 'bus.c', 'region.c', 'memdev.c', 'json.c', 'filter.c', '../daxctl/json.c', '../daxctl/filter.c', ] if get_option('systemd').enabled() install_data('cxl-monitor.service', install_dir : systemdunitdir) endif deps = [ cxl_dep, daxctl_dep, util_dep, uuid, kmod, json, versiondep, ] if get_option('libtracefs').enabled() cxl_src += [ '../util/event_trace.c', 'monitor.c', ] deps += [ traceevent, tracefs, ] endif cxl_tool = executable('cxl', cxl_src, include_directories : root_inc, dependencies : deps, install : true, install_dir : rootbindir, ) install_headers('libcxl.h', subdir : 'cxl') ndctl-81/cxl/monitor.c000066400000000000000000000115411476737544500150610ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2022, Intel Corp. All rights reserved. /* Some bits copied from ndctl monitor code */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* reuse the core log helpers for the monitor logger */ #ifndef ENABLE_LOGGING #define ENABLE_LOGGING #endif #ifndef ENABLE_DEBUG #define ENABLE_DEBUG #endif #include #include static const char *cxl_system = "cxl"; const char *default_log = "/var/log/cxl-monitor.log"; static struct monitor { const char *log; struct log_ctx ctx; bool human; bool verbose; bool daemon; } monitor; static int monitor_event(struct cxl_ctx *ctx) { int fd, epollfd, rc = 0, timeout = -1; struct epoll_event ev, *events; struct tracefs_instance *inst; struct event_ctx ectx; int jflag; events = calloc(1, sizeof(struct epoll_event)); if (!events) { err(&monitor, "alloc for events error\n"); return -ENOMEM; } epollfd = epoll_create1(0); if (epollfd == -1) { rc = -errno; err(&monitor, "epoll_create1() error: %d\n", rc); goto epoll_err; } inst = tracefs_instance_create("cxl_monitor"); if (!inst) { rc = -errno; err(&monitor, "tracefs_instance_create( failed: %d\n", rc); goto inst_err; } fd = tracefs_instance_file_open(inst, "trace_pipe", -1); if (fd < 0) { rc = fd; err(&monitor, "tracefs_instance_file_open() err: %d\n", rc); goto inst_file_err; } memset(&ev, 0, sizeof(ev)); ev.events = EPOLLIN; ev.data.fd = fd; if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev) != 0) { rc = -errno; err(&monitor, "epoll_ctl() error: %d\n", rc); goto epoll_ctl_err; } rc = trace_event_enable(inst, cxl_system, NULL); if (rc < 0) { err(&monitor, "trace_event_enable() failed: %d\n", rc); goto event_en_err; } memset(&ectx, 0, sizeof(ectx)); ectx.system = cxl_system; if (monitor.human) jflag = JSON_C_TO_STRING_PRETTY; else jflag = JSON_C_TO_STRING_PLAIN; while (1) { struct jlist_node *jnode, *next; rc = epoll_wait(epollfd, events, 1, timeout); if (rc < 0) { rc = -errno; if (errno != EINTR) err(&monitor, "epoll_wait error: %d\n", -errno); break; } list_head_init(&ectx.jlist_head); rc = trace_event_parse(inst, &ectx); if (rc < 0) goto parse_err; if (list_empty(&ectx.jlist_head)) continue; list_for_each_safe(&ectx.jlist_head, jnode, next, list) { notice(&monitor, "%s\n", json_object_to_json_string_ext(jnode->jobj, jflag)); list_del(&jnode->list); json_object_put(jnode->jobj); free(jnode); } } parse_err: if (trace_event_disable(inst) < 0) err(&monitor, "failed to disable tracing\n"); event_en_err: epoll_ctl_err: close(fd); inst_file_err: tracefs_instance_free(inst); inst_err: close(epollfd); epoll_err: free(events); return rc; } int cmd_monitor(int argc, const char **argv, struct cxl_ctx *ctx) { const struct option options[] = { OPT_FILENAME('l', "log", &monitor.log, " | standard", "where to output the monitor's notification"), OPT_BOOLEAN('\0', "daemon", &monitor.daemon, "run cxl monitor as a daemon"), OPT_BOOLEAN('u', "human", &monitor.human, "use human friendly output formats"), OPT_BOOLEAN('v', "verbose", &monitor.verbose, "emit extra debug messages to log"), OPT_END(), }; const char * const u[] = { "cxl monitor []", NULL }; const char *prefix ="./"; int rc = 0, i; const char *log; argc = parse_options_prefix(argc, argv, prefix, options, u, 0); for (i = 0; i < argc; i++) error("unknown parameter \"%s\"\n", argv[i]); if (argc) usage_with_options(u, options); /* sanity check */ if (monitor.daemon && monitor.log && !strncmp(monitor.log, "./", 2)) { error("relative path or 'standard' are not compatible with daemon mode\n"); return -EINVAL; } log_init(&monitor.ctx, "cxl/monitor", "CXL_MONITOR_LOG"); if (monitor.log) log = monitor.log; else log = monitor.daemon ? default_log : "./standard"; if (monitor.verbose) monitor.ctx.log_priority = LOG_DEBUG; else monitor.ctx.log_priority = LOG_INFO; if (strcmp(log, "./standard") == 0) monitor.ctx.log_fn = log_standard; else { monitor.ctx.log_file = fopen(log, "a+"); if (!monitor.ctx.log_file) { rc = -errno; error("open %s failed: %d\n", log, rc); goto out; } monitor.ctx.log_fn = log_file; } if (monitor.daemon) { if (daemon(0, 0) != 0) { err(&monitor, "daemon start failed\n"); goto out; } info(&monitor, "cxl monitor daemon started.\n"); } rc = monitor_event(ctx); out: if (monitor.ctx.log_file) fclose(monitor.ctx.log_file); return rc; } ndctl-81/cxl/port.c000066400000000000000000000130121476737544500143510ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) 2020-2022 Intel Corporation. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include "filter.h" static struct parameters { bool debug; bool force; bool memdevs; bool endpoint; } param; static struct log_ctx pl; #define BASE_OPTIONS() \ OPT_BOOLEAN(0, "debug", ¶m.debug, "turn on debug"), \ OPT_BOOLEAN('e', "endpoint", ¶m.endpoint, \ "target endpoints instead of switch ports") #define ENABLE_OPTIONS() \ OPT_BOOLEAN('m', "enable-memdevs", ¶m.memdevs, \ "enable downstream memdev(s)") #define DISABLE_OPTIONS() \ OPT_BOOLEAN('f', "force", ¶m.force, \ "DANGEROUS: override active memdev safety checks") static const struct option disable_options[] = { BASE_OPTIONS(), DISABLE_OPTIONS(), OPT_END(), }; static const struct option enable_options[] = { BASE_OPTIONS(), ENABLE_OPTIONS(), OPT_END(), }; static int action_disable(struct cxl_port *port) { const char *devname = cxl_port_get_devname(port); struct cxl_ctx *ctx = cxl_port_get_ctx(port); struct cxl_memdev *memdev; int active_memdevs = 0; if (!cxl_port_is_enabled(port)) { log_dbg(&pl, "%s already disabled\n", devname); return 0; } if (param.endpoint) { struct cxl_endpoint *endpoint = cxl_port_to_endpoint(port); if (cxl_endpoint_get_memdev(endpoint)) active_memdevs++; } cxl_memdev_foreach(ctx, memdev) { if (!cxl_port_get_dport_by_memdev(port, memdev)) continue; if (cxl_memdev_is_enabled(memdev)) active_memdevs++; } if (active_memdevs && !param.force) { /* * TODO: actually detect rather than assume active just * because the memdev is enabled */ log_err(&pl, "%s hosts %d memdev%s which %s part of an active region\n", devname, active_memdevs, active_memdevs > 1 ? "s" : "", active_memdevs > 1 ? "are" : "is"); log_err(&pl, "See 'cxl list -M -p %s' to see impacted device%s\n", devname, active_memdevs > 1 ? "s" : ""); return -EBUSY; } return cxl_port_disable_invalidate(port); } static int action_enable(struct cxl_port *port) { struct cxl_ctx *ctx = cxl_port_get_ctx(port); struct cxl_memdev *memdev; int rc; rc = cxl_port_enable(port); if (rc || !param.memdevs) return rc; cxl_memdev_foreach(ctx, memdev) if (cxl_port_get_dport_by_memdev(port, memdev)) cxl_memdev_enable(memdev); return 0; } static struct cxl_port *find_cxl_port(struct cxl_ctx *ctx, const char *ident) { struct cxl_bus *bus; struct cxl_port *port; cxl_bus_foreach(ctx, bus) cxl_port_foreach_all(cxl_bus_get_port(bus), port) if (util_cxl_port_filter(port, ident, CXL_PF_SINGLE)) return port; return NULL; } static struct cxl_endpoint *find_cxl_endpoint(struct cxl_ctx *ctx, const char *ident) { struct cxl_bus *bus; struct cxl_port *port; struct cxl_endpoint *endpoint; cxl_bus_foreach(ctx, bus) cxl_port_foreach_all(cxl_bus_get_port(bus), port) cxl_endpoint_foreach(port, endpoint) if (util_cxl_endpoint_filter(endpoint, ident)) return endpoint; return NULL; } static int port_action(int argc, const char **argv, struct cxl_ctx *ctx, int (*action)(struct cxl_port *port), const struct option *options, const char *usage) { int i, rc = 0, count = 0, err = 0; const char * const u[] = { usage, NULL }; log_init(&pl, "cxl port", "CXL_PORT_LOG"); argc = parse_options(argc, argv, options, u, 0); if (argc == 0) usage_with_options(u, options); for (i = 0; i < argc; i++) { if (strcmp(argv[i], "all") == 0) { argc = 1; break; } } if (param.debug) { cxl_set_log_priority(ctx, LOG_DEBUG); pl.log_priority = LOG_DEBUG; } else pl.log_priority = LOG_INFO; rc = 0; count = 0; for (i = 0; i < argc; i++) { struct cxl_port *port; if (param.endpoint) { struct cxl_endpoint *endpoint; endpoint = find_cxl_endpoint(ctx, argv[i]); if (!endpoint) { log_notice(&pl, "endpoint: %s not found\n", argv[i]); continue; } port = cxl_endpoint_get_port(endpoint); } else { port = find_cxl_port(ctx, argv[i]); if (!port) { log_notice(&pl, "port: %s not found\n", argv[i]); continue; } } log_dbg(&pl, "run action on port: %s\n", cxl_port_get_devname(port)); rc = action(port); if (rc == 0) count++; else if (rc && !err) err = rc; } rc = err; /* * count if some actions succeeded, 0 if none were attempted, * negative error code otherwise. */ if (count > 0) return count; return rc; } int cmd_disable_port(int argc, const char **argv, struct cxl_ctx *ctx) { int count = port_action( argc, argv, ctx, action_disable, disable_options, "cxl disable-port [..] []"); log_info(&pl, "disabled %d port%s\n", count >= 0 ? count : 0, count > 1 ? "s" : ""); return count >= 0 ? 0 : EXIT_FAILURE; } int cmd_enable_port(int argc, const char **argv, struct cxl_ctx *ctx) { int count = port_action( argc, argv, ctx, action_enable, enable_options, "cxl enable-port [..] []"); log_info(&pl, "enabled %d port%s\n", count >= 0 ? count : 0, count > 1 ? "s" : ""); return count >= 0 ? 0 : EXIT_FAILURE; } ndctl-81/cxl/region.c000066400000000000000000000606511476737544500146630ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) 2020-2022 Intel Corporation. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "filter.h" #include "json.h" static struct region_params { const char *bus; const char *size; const char *type; const char *uuid; const char *root_decoder; const char *region; int ways; int granularity; bool memdevs; bool force; bool human; bool debug; bool enforce_qos; } param = { .ways = INT_MAX, .granularity = INT_MAX, }; struct parsed_params { u64 size; u64 ep_min_size; int ways; int granularity; uuid_t uuid; struct json_object *memdevs; int num_memdevs; int argc; const char **argv; struct cxl_decoder *root_decoder; enum cxl_decoder_mode mode; bool enforce_qos; }; enum region_actions { ACTION_CREATE, ACTION_ENABLE, ACTION_DISABLE, ACTION_DESTROY, }; static struct log_ctx rl; #define BASE_OPTIONS() \ OPT_STRING('b', "bus", ¶m.bus, "bus name", \ "Limit operation to the specified bus"), \ OPT_STRING('d', "decoder", ¶m.root_decoder, "root decoder name", \ "Limit to / use the specified root decoder"), \ OPT_BOOLEAN(0, "debug", ¶m.debug, "turn on debug") #define CREATE_OPTIONS() \ OPT_STRING('s', "size", ¶m.size, \ "size in bytes or with a K/M/G etc. suffix", \ "total size desired for the resulting region."), \ OPT_INTEGER('w', "ways", ¶m.ways, \ "number of memdevs participating in the regions interleave set"), \ OPT_INTEGER('g', "granularity", ¶m.granularity, \ "granularity of the interleave set"), \ OPT_STRING('t', "type", ¶m.type, \ "region type", "region type - 'pmem' or 'ram'"), \ OPT_STRING('U', "uuid", ¶m.uuid, \ "region uuid", "uuid for the new region (default: autogenerate)"), \ OPT_BOOLEAN('m', "memdevs", ¶m.memdevs, \ "non-option arguments are memdevs"), \ OPT_BOOLEAN('u', "human", ¶m.human, "use human friendly number formats"), \ OPT_BOOLEAN('Q', "enforce-qos", ¶m.enforce_qos, "enforce qos_class match") static const struct option create_options[] = { BASE_OPTIONS(), CREATE_OPTIONS(), OPT_END(), }; static const struct option enable_options[] = { BASE_OPTIONS(), OPT_END(), }; static const struct option disable_options[] = { BASE_OPTIONS(), OPT_BOOLEAN('f', "force", ¶m.force, "attempt to offline memory before disabling the region"), OPT_END(), }; static const struct option destroy_options[] = { BASE_OPTIONS(), OPT_BOOLEAN('f', "force", ¶m.force, "destroy region even if currently active"), OPT_END(), }; /* * Convert an array of strings that can be a mixture of single items, a * command separated list, or a space separated list, into a flattened * comma-separated string. That single string can then be used as a * filter argument to cxl_filter_walk(), or an ordering constraint for * json_object_array_sort() * * On entry @count is the number of elements in the strings array, on * exit, @count is the number of elements in the csv. */ static const char *to_csv(int *count, const char **strings) { ssize_t len = 0, cursor = 0; char *csv, *list, *save; int i, new_count = 0; const char *arg; if (!*count) return NULL; for (i = 0; i < *count; i++) { /* * An entry in strings may itself by a space or comma * separated list, so decompose that for the final csv */ list = strdup(strings[i]); if (!list) return NULL; for (arg = strtok_r(list, which_sep(list), &save); arg; arg = strtok_r(NULL, which_sep(list), &save)) { len += strlen(arg); new_count++; } free(list); } len += new_count + 1; csv = calloc(1, len); if (!csv) return NULL; for (i = 0; i < *count; i++) { list = strdup(strings[i]); if (!list) { free(csv); return NULL; } for (arg = strtok_r(list, which_sep(list), &save); arg; arg = strtok_r(NULL, which_sep(list), &save)) { cursor += snprintf(csv + cursor, len - cursor, "%s%s", arg, i + 1 < new_count ? "," : ""); if (cursor >= len) { csv[len - 1] = 0; break; } } free(list); } *count = new_count; return csv; } static struct sort_context { const char *csv; } sort_context; static int memdev_filter_pos(struct json_object *jobj, const char *_csv) { struct cxl_memdev *memdev = json_object_get_userdata(jobj); const char *sep = which_sep(_csv); char *csv, *save; const char *arg; int pos; csv = strdup(_csv); if (!csv) return -1; for (pos = 0, arg = strtok_r(csv, sep, &save); arg; arg = strtok_r(NULL, sep, &save), pos++) if (util_cxl_memdev_filter(memdev, arg, NULL)) break; free(csv); return pos; } static int memdev_sort(const void *a, const void *b) { struct json_object **a_obj, **b_obj; int a_pos, b_pos; a_obj = (struct json_object **) a; b_obj = (struct json_object **) b; a_pos = memdev_filter_pos(*a_obj, sort_context.csv); b_pos = memdev_filter_pos(*b_obj, sort_context.csv); if (a_pos < 0 || b_pos < 0) return 0; return a_pos - b_pos; } static struct json_object *collect_memdevs(struct cxl_ctx *ctx, const char *decoder, int *count, const char **mems) { const char *csv = to_csv(count, mems); struct cxl_filter_params filter_params = { .decoder_filter = decoder, .memdevs = true, .memdev_filter = csv, }; struct json_object *jmemdevs; jmemdevs = cxl_filter_walk(ctx, &filter_params); if (!jmemdevs) { log_err(&rl, "failed to retrieve memdevs\n"); goto out; } if (json_object_array_length(jmemdevs) == 0) { log_err(&rl, "no active memdevs found: decoder: %s filter: %s\n", decoder, csv ? csv : "none"); json_object_put(jmemdevs); jmemdevs = NULL; goto out; } if (csv) { sort_context.csv = csv, json_object_array_sort(jmemdevs, memdev_sort); } out: free((void *)csv); return jmemdevs; } static bool validate_ways(struct parsed_params *p, int count) { /* * Validate interleave ways against targets found in the topology. If * the targets were specified, then non-default param.ways must equal * that number of targets. */ if (p->ways > p->num_memdevs || (count && p->ways != p->num_memdevs)) { log_err(&rl, "Interleave ways %d is %s than number of memdevs %s: %d\n", p->ways, p->ways > p->num_memdevs ? "greater" : "less", count ? "specified" : "found", p->num_memdevs); return false; } return true; } static int parse_create_options(struct cxl_ctx *ctx, int count, const char **mems, struct parsed_params *p) { if (!param.root_decoder) { log_err(&rl, "no root decoder specified\n"); return -EINVAL; } /* * For all practical purposes, -m is the default target type, but hold * off on actively making that decision until a second target option is * available. Unless there are no arguments then just assume memdevs. */ if (!count) param.memdevs = true; if (!param.memdevs) { log_err(&rl, "must specify option for target object types (-m)\n"); return -EINVAL; } /* Collect the valid memdevs relative to the given root decoder */ p->memdevs = collect_memdevs(ctx, param.root_decoder, &count, mems); if (!p->memdevs) return -ENXIO; p->num_memdevs = json_object_array_length(p->memdevs); if (param.type) { p->mode = cxl_decoder_mode_from_ident(param.type); if (p->mode == CXL_DECODER_MODE_RAM && param.uuid) { log_err(&rl, "can't set UUID for ram / volatile regions"); goto err; } if (p->mode == CXL_DECODER_MODE_NONE) { log_err(&rl, "unsupported type: %s\n", param.type); goto err; } } else { p->mode = CXL_DECODER_MODE_PMEM; } if (param.size) { p->size = parse_size64(param.size); if (p->size == ULLONG_MAX) { log_err(&rl, "Invalid size: %s\n", param.size); goto err; } } if (param.ways <= 0) { log_err(&rl, "Invalid interleave ways: %d\n", param.ways); goto err; } else if (param.ways < INT_MAX) { p->ways = param.ways; if (!validate_ways(p, count)) goto err; } else if (count) { p->ways = count; if (!validate_ways(p, count)) goto err; } else p->ways = p->num_memdevs; if (param.granularity < INT_MAX) { if (param.granularity <= 0) { log_err(&rl, "Invalid interleave granularity: %d\n", param.granularity); goto err; } p->granularity = param.granularity; } if (p->size && p->ways) { if (p->size % p->ways) { log_err(&rl, "size (%lu) is not an integral multiple of interleave-ways (%u)\n", p->size, p->ways); goto err; } } if (param.uuid) { if (uuid_parse(param.uuid, p->uuid)) { error("failed to parse uuid: '%s'\n", param.uuid); goto err; } } p->enforce_qos = param.enforce_qos; return 0; err: json_object_put(p->memdevs); return -EINVAL; } static int parse_region_options(int argc, const char **argv, struct cxl_ctx *ctx, enum region_actions action, const struct option *options, struct parsed_params *p, const char *usage) { const char * const u[] = { usage, NULL }; argc = parse_options(argc, argv, options, u, 0); p->argc = argc; p->argv = argv; if (param.debug) { cxl_set_log_priority(ctx, LOG_DEBUG); rl.log_priority = LOG_DEBUG; } else rl.log_priority = LOG_INFO; switch(action) { case ACTION_CREATE: return parse_create_options(ctx, argc, argv, p); default: return 0; } } static void collect_minsize(struct cxl_ctx *ctx, struct parsed_params *p) { int i; for (i = 0; i < p->ways; i++) { struct json_object *jobj = json_object_array_get_idx(p->memdevs, i); struct cxl_memdev *memdev = json_object_get_userdata(jobj); u64 size = 0; switch(p->mode) { case CXL_DECODER_MODE_RAM: size = cxl_memdev_get_ram_size(memdev); break; case CXL_DECODER_MODE_PMEM: size = cxl_memdev_get_pmem_size(memdev); break; default: /* Shouldn't ever get here */ ; } if (!p->ep_min_size) p->ep_min_size = size; else p->ep_min_size = min(p->ep_min_size, size); } } static int create_region_validate_qos_class(struct parsed_params *p) { int root_qos_class; int qos_class; int i; if (!p->enforce_qos) return 0; root_qos_class = cxl_root_decoder_get_qos_class(p->root_decoder); if (root_qos_class == CXL_QOS_CLASS_NONE) return 0; for (i = 0; i < p->ways; i++) { struct json_object *jobj = json_object_array_get_idx(p->memdevs, i); struct cxl_memdev *memdev = json_object_get_userdata(jobj); if (p->mode == CXL_DECODER_MODE_RAM) qos_class = cxl_memdev_get_ram_qos_class(memdev); else qos_class = cxl_memdev_get_pmem_qos_class(memdev); /* No qos_class entries. Possibly no kernel support */ if (qos_class == CXL_QOS_CLASS_NONE) break; if (qos_class != root_qos_class) { if (p->enforce_qos) { log_err(&rl, "%s qos_class mismatch %s\n", cxl_decoder_get_devname(p->root_decoder), cxl_memdev_get_devname(memdev)); return -ENXIO; } } } return 0; } static int validate_decoder(struct cxl_decoder *decoder, struct parsed_params *p) { const char *devname = cxl_decoder_get_devname(decoder); int rc; switch(p->mode) { case CXL_DECODER_MODE_RAM: if (!cxl_decoder_is_volatile_capable(decoder)) { log_err(&rl, "%s is not volatile capable\n", devname); return -EINVAL; } break; case CXL_DECODER_MODE_PMEM: if (!cxl_decoder_is_pmem_capable(decoder)) { log_err(&rl, "%s is not pmem capable\n", devname); return -EINVAL; } break; default: log_err(&rl, "unknown type: %s\n", param.type); return -EINVAL; } rc = create_region_validate_qos_class(p); if (rc) return rc; /* TODO check if the interleave config is possible under this decoder */ return 0; } static void set_type_from_decoder(struct cxl_ctx *ctx, struct parsed_params *p) { /* if param.type was explicitly specified, nothing to do here */ if (param.type) return; /* * default to pmem if both types are set, otherwise the single * capability dominates. */ if (cxl_decoder_is_volatile_capable(p->root_decoder)) p->mode = CXL_DECODER_MODE_RAM; if (cxl_decoder_is_pmem_capable(p->root_decoder)) p->mode = CXL_DECODER_MODE_PMEM; } static int create_region_validate_config(struct cxl_ctx *ctx, struct parsed_params *p) { struct cxl_bus *bus; int rc; cxl_bus_foreach(ctx, bus) { struct cxl_decoder *decoder; struct cxl_port *port; if (!util_cxl_bus_filter(bus, param.bus)) continue; port = cxl_bus_get_port(bus); if (!cxl_port_is_root(port)) continue; cxl_decoder_foreach (port, decoder) { if (util_cxl_decoder_filter(decoder, param.root_decoder)) { p->root_decoder = decoder; goto found; } } } found: if (p->root_decoder == NULL) { log_err(&rl, "%s not found in CXL topology\n", param.root_decoder); return -ENXIO; } set_type_from_decoder(ctx, p); rc = validate_decoder(p->root_decoder, p); if (rc) return rc; collect_minsize(ctx, p); return 0; } static struct cxl_decoder *cxl_memdev_find_decoder(struct cxl_memdev *memdev) { const char *memdev_name = cxl_memdev_get_devname(memdev); struct cxl_decoder *decoder; struct cxl_endpoint *ep; struct cxl_port *port; ep = cxl_memdev_get_endpoint(memdev); if (!ep) { log_err(&rl, "could not get an endpoint for %s\n", memdev_name); return NULL; } port = cxl_endpoint_get_port(ep); if (!port) { log_err(&rl, "could not get a port for %s\n", memdev_name); return NULL; } cxl_decoder_foreach(port, decoder) if (cxl_decoder_get_size(decoder) == 0) return decoder; log_err(&rl, "could not get a free decoder for %s\n", memdev_name); return NULL; } #define try(prefix, op, dev, p) \ do { \ int __rc = prefix##_##op(dev, p); \ if (__rc) { \ log_err(&rl, "%s: " #op " failed: %s\n", \ prefix##_get_devname(dev), \ strerror(abs(__rc))); \ rc = __rc; \ goto out; \ } \ } while (0) static int cxl_region_determine_granularity(struct cxl_region *region, struct parsed_params *p) { const char *devname = cxl_region_get_devname(region); int granularity, ways; /* Default granularity will be the root decoder's granularity */ granularity = cxl_decoder_get_interleave_granularity(p->root_decoder); if (granularity == 0 || granularity == -1) { log_err(&rl, "%s: unable to determine root decoder granularity\n", devname); return -ENXIO; } /* If no user-supplied granularity, just use the default */ if (!p->granularity) return granularity; ways = cxl_decoder_get_interleave_ways(p->root_decoder); if (ways == 0 || ways == -1) { log_err(&rl, "%s: unable to determine root decoder ways\n", devname); return -ENXIO; } /* For ways == 1, any user-supplied granularity is fine */ if (ways == 1) return p->granularity; /* * For ways > 1, only allow the same granularity as the selected * root decoder */ if (p->granularity == granularity) return granularity; log_err(&rl, "%s: For an x%d root, only root decoder granularity (%d) permitted\n", devname, ways, granularity); return -EINVAL; } static int create_region(struct cxl_ctx *ctx, int *count, struct parsed_params *p) { unsigned long flags = UTIL_JSON_TARGETS; struct json_object *jregion; struct cxl_region *region; bool default_size = true; int i, rc, granularity; u64 size, max_extent; const char *devname; rc = create_region_validate_config(ctx, p); if (rc) return rc; if (p->size) { size = p->size; default_size = false; } else if (p->ep_min_size) { size = p->ep_min_size * p->ways; } else { log_err(&rl, "unable to determine region size\n"); return -ENXIO; } max_extent = cxl_decoder_get_max_available_extent(p->root_decoder); if (max_extent == ULLONG_MAX) { log_err(&rl, "%s: unable to determine max extent\n", cxl_decoder_get_devname(p->root_decoder)); return -EINVAL; } if (!default_size && size > max_extent) { log_err(&rl, "%s: region size %#lx exceeds max available space (%#lx)\n", cxl_decoder_get_devname(p->root_decoder), size, max_extent); return -ENOSPC; } if (size > max_extent) size = ALIGN_DOWN(max_extent, SZ_256M * p->ways); if (p->mode == CXL_DECODER_MODE_PMEM) { region = cxl_decoder_create_pmem_region(p->root_decoder); if (!region) { log_err(&rl, "failed to create region under %s\n", param.root_decoder); return -ENXIO; } } else if (p->mode == CXL_DECODER_MODE_RAM) { region = cxl_decoder_create_ram_region(p->root_decoder); if (!region) { log_err(&rl, "failed to create region under %s\n", param.root_decoder); return -ENXIO; } } else { log_err(&rl, "region type '%s' is not supported\n", param.type); return -EOPNOTSUPP; } devname = cxl_region_get_devname(region); rc = cxl_region_determine_granularity(region, p); if (rc < 0) goto out; granularity = rc; try(cxl_region, set_interleave_granularity, region, granularity); try(cxl_region, set_interleave_ways, region, p->ways); if (p->mode == CXL_DECODER_MODE_PMEM) { if (!param.uuid) uuid_generate(p->uuid); try(cxl_region, set_uuid, region, p->uuid); } try(cxl_region, set_size, region, size); for (i = 0; i < p->ways; i++) { struct cxl_decoder *ep_decoder; struct json_object *jobj = json_object_array_get_idx(p->memdevs, i); struct cxl_memdev *memdev = json_object_get_userdata(jobj); ep_decoder = cxl_memdev_find_decoder(memdev); if (!ep_decoder) { rc = -ENXIO; goto out; } if (cxl_decoder_get_mode(ep_decoder) != p->mode) { /* * The cxl_memdev_find_decoder() helper returns a free * decoder whose size has been checked for 0. * Thus it is safe to change the mode here if needed. */ try(cxl_decoder, set_dpa_size, ep_decoder, 0); try(cxl_decoder, set_mode, ep_decoder, p->mode); } try(cxl_decoder, set_dpa_size, ep_decoder, size/p->ways); rc = cxl_region_set_target(region, i, ep_decoder); if (rc) { log_err(&rl, "%s: failed to set target%d to %s\n", devname, i, cxl_memdev_get_devname(memdev)); goto out; } } rc = cxl_region_decode_commit(region); if (rc) { log_err(&rl, "%s: failed to commit decode: %s\n", devname, strerror(-rc)); goto out; } rc = cxl_region_enable(region); if (rc) { log_err(&rl, "%s: failed to enable: %s\n", devname, strerror(-rc)); goto out; } *count = 1; if (isatty(1)) flags |= UTIL_JSON_HUMAN; jregion = util_cxl_region_to_json(region, flags); if (jregion) printf("%s\n", json_object_to_json_string_ext(jregion, JSON_C_TO_STRING_PRETTY)); out: json_object_put(p->memdevs); if (rc) cxl_region_delete(region); return rc; } static int disable_region(struct cxl_region *region) { const char *devname = cxl_region_get_devname(region); struct daxctl_region *dax_region; struct daxctl_memory *mem; struct daxctl_dev *dev; int failed = 0, rc; dax_region = cxl_region_get_daxctl_region(region); if (!dax_region) goto out; daxctl_dev_foreach(dax_region, dev) { if (!daxctl_dev_is_system_ram_capable(dev)) continue; mem = daxctl_dev_get_memory(dev); if (!mem) return -ENXIO; /* * If memory is still online and user wants to force it, attempt * to offline it. */ if (daxctl_memory_is_online(mem)) { rc = daxctl_memory_offline(mem); if (rc < 0) { log_err(&rl, "%s: unable to offline %s: %s\n", devname, daxctl_dev_get_devname(dev), strerror(abs(rc))); if (!param.force) return rc; failed++; } } } if (failed) { log_err(&rl, "%s: Forcing region disable without successful offline.\n", devname); log_err(&rl, "%s: Physical address space has now been permanently leaked.\n", devname); log_err(&rl, "%s: Leaked address cannot be recovered until a reboot.\n", devname); } out: return cxl_region_disable(region); } static int destroy_region(struct cxl_region *region) { const char *devname = cxl_region_get_devname(region); unsigned int ways, i; int rc; /* First, unbind/disable the region if needed */ if (cxl_region_is_enabled(region)) { if (param.force) { rc = disable_region(region); if (rc) { log_err(&rl, "%s: error disabling region: %s\n", devname, strerror(-rc)); return rc; } } else { log_err(&rl, "%s active. Disable it or use --force\n", devname); return -EBUSY; } } /* Reset the region decode in preparation for removal */ rc = cxl_region_decode_reset(region); if (rc) { log_err(&rl, "%s: failed to reset decode: %s\n", devname, strerror(-rc)); return rc; } /* Reset all endpoint decoders and region targets */ ways = cxl_region_get_interleave_ways(region); if (ways == 0 || ways == UINT_MAX) { log_err(&rl, "%s: error getting interleave ways\n", devname); return -ENXIO; } for (i = 0; i < ways; i++) { struct cxl_decoder *ep_decoder; ep_decoder = cxl_region_get_target_decoder(region, i); if (!ep_decoder) return -ENXIO; rc = cxl_region_clear_target(region, i); if (rc) { log_err(&rl, "%s: clearing target%d failed: %s\n", devname, i, strerror(abs(rc))); return rc; } rc = cxl_decoder_set_dpa_size(ep_decoder, 0); if (rc) { log_err(&rl, "%s: set_dpa_size failed: %s\n", cxl_decoder_get_devname(ep_decoder), strerror(abs(rc))); return rc; } } /* Finally, delete the region */ return cxl_region_delete(region); } static int do_region_xable(struct cxl_region *region, enum region_actions action) { switch (action) { case ACTION_ENABLE: return cxl_region_enable(region); case ACTION_DISABLE: return disable_region(region); case ACTION_DESTROY: return destroy_region(region); default: return -EINVAL; } } static int decoder_region_action(struct parsed_params *p, struct cxl_decoder *decoder, enum region_actions action, int *count) { struct cxl_region *region, *_r; int rc = 0, err_rc = 0; cxl_region_foreach_safe (decoder, region, _r) { int i, match = 0; for (i = 0; i < p->argc; i++) { if (util_cxl_region_filter(region, p->argv[i])) { match = 1; break; } } if (!match) continue; rc = do_region_xable(region, action); if (rc == 0) { *count += 1; } else { log_err(&rl, "%s: failed: %s\n", cxl_region_get_devname(region), strerror(-rc)); err_rc = rc; } } return err_rc ? err_rc : rc; } static int region_action(int argc, const char **argv, struct cxl_ctx *ctx, enum region_actions action, const struct option *options, struct parsed_params *p, int *count, const char *u) { int rc = 0, err_rc = 0; struct cxl_bus *bus; log_init(&rl, "cxl region", "CXL_REGION_LOG"); rc = parse_region_options(argc, argv, ctx, action, options, p, u); if (rc) return rc; if (action == ACTION_CREATE) return create_region(ctx, count, p); cxl_bus_foreach(ctx, bus) { struct cxl_decoder *decoder; struct cxl_port *port; if (!util_cxl_bus_filter(bus, param.bus)) continue; port = cxl_bus_get_port(bus); if (!cxl_port_is_root(port)) continue; cxl_decoder_foreach (port, decoder) { if (!util_cxl_decoder_filter(decoder, param.root_decoder)) continue; rc = decoder_region_action(p, decoder, action, count); if (rc) err_rc = rc; } } if (err_rc) { log_err(&rl, "one or more failures, last failure: %s\n", strerror(-err_rc)); return err_rc; } return rc; } int cmd_create_region(int argc, const char **argv, struct cxl_ctx *ctx) { const char *u = "cxl create-region ... []"; struct parsed_params p = { 0 }; int rc, count = 0; rc = region_action(argc, argv, ctx, ACTION_CREATE, create_options, &p, &count, u); log_info(&rl, "created %d region%s\n", count, count == 1 ? "" : "s"); return rc == 0 ? 0 : EXIT_FAILURE; } int cmd_enable_region(int argc, const char **argv, struct cxl_ctx *ctx) { const char *u = "cxl enable-region ... []"; struct parsed_params p = { 0 }; int rc, count = 0; rc = region_action(argc, argv, ctx, ACTION_ENABLE, enable_options, &p, &count, u); log_info(&rl, "enabled %d region%s\n", count, count == 1 ? "" : "s"); return rc == 0 ? 0 : EXIT_FAILURE; } int cmd_disable_region(int argc, const char **argv, struct cxl_ctx *ctx) { const char *u = "cxl disable-region ... []"; struct parsed_params p = { 0 }; int rc, count = 0; rc = region_action(argc, argv, ctx, ACTION_DISABLE, disable_options, &p, &count, u); log_info(&rl, "disabled %d region%s\n", count, count == 1 ? "" : "s"); return rc == 0 ? 0 : EXIT_FAILURE; } int cmd_destroy_region(int argc, const char **argv, struct cxl_ctx *ctx) { const char *u = "cxl destroy-region ... []"; struct parsed_params p = { 0 }; int rc, count = 0; rc = region_action(argc, argv, ctx, ACTION_DESTROY, destroy_options, &p, &count, u); log_info(&rl, "destroyed %d region%s\n", count, count == 1 ? "" : "s"); return rc == 0 ? 0 : EXIT_FAILURE; } ndctl-81/daxctl/000077500000000000000000000000001476737544500137155ustar00rootroot00000000000000ndctl-81/daxctl/90-daxctl-device.rules000066400000000000000000000002541476737544500177340ustar00rootroot00000000000000ACTION=="add", SUBSYSTEM=="dax", TAG+="systemd", \ PROGRAM="/usr/bin/systemd-escape -p --template=daxdev-reconfigure@.service $env{DEVNAME}", \ ENV{SYSTEMD_WANTS}="%c" ndctl-81/daxctl/acpi.c000066400000000000000000000462461476737544500150110ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) 2017-2020 Intel Corporation. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include static bool verbose; struct srat_container { struct srat *srat; struct list_head ents; }; struct srat_ent { struct list_node list; struct acpi_subtable8 *tbl; }; struct nfit_container { struct nfit *nfit; struct list_head ents; }; struct nfit_ent { struct list_node list; struct acpi_subtable16 *tbl; }; static void free_srat_container(struct srat_container *container) { struct srat_ent *ent, *_e; if (!container) return; list_for_each_safe(&container->ents, ent, _e, list) { list_del_from(&container->ents, &ent->list); free(ent); } free(container->srat); free(container); } static void free_nfit_container(struct nfit_container *container) { struct nfit_ent *ent, *_e; if (!container) return; list_for_each_safe(&container->ents, ent, _e, list) { list_del_from(&container->ents, &ent->list); free(ent); } free(container->nfit); free(container); } static void *read_table(int fd, const char *sig) { int rc, len; uint8_t checksum; struct acpi_header hdr; struct acpi_header *data = NULL; rc = read(fd, &hdr, sizeof(hdr)); if (rc < (int) sizeof(hdr)) { error("failed to read header\n"); rc = rc < 0 ? -errno : -EINVAL; goto out; } if (strncmp((char *) hdr.signature, sig, 4) != 0) { error("invalid %s header\n", sig); rc = -EINVAL; goto out; } data = calloc(1, hdr.length); if (!data) { error("failed to alloc %d bytes\n", hdr.length); rc = -ENOMEM; goto out; } for (len = hdr.length; len > 0;) { int offset = hdr.length - len; rc = pread(fd, ((char *) data) + offset, len, offset); if (rc < 0) break; len -= rc; } if (rc < 0) { error("failed to read %s\n", sig); rc = -errno; goto out; } checksum = data->checksum; data->checksum = 0; if (acpi_checksum(data, data->length) != checksum) { error("bad %s checksum\n", sig); rc = -EINVAL; goto out; } out: close(fd); if (rc < 0) { free(data); data = NULL; } return data; } static struct nfit_container *read_nfit(int fd) { void *buf; int rc = 0; unsigned int length; struct nfit *nfit = NULL; struct nfit_container *container = NULL; nfit = read_table(fd, "NFIT"); if (!nfit) return NULL; container = calloc(1, sizeof(*container)); if (!container) { error("failed to alloc %d bytes\n", nfit->h.length); rc = -ENOMEM; goto out; } list_head_init(&container->ents); container->nfit = nfit; length = nfit->h.length - sizeof(*nfit); if (!length) { error("no sub-tables found in SRAT\n"); rc = -EINVAL; goto out; } buf = nfit + 1; while (length) { struct nfit_ent *ent = calloc(1, sizeof(*ent)); if (!ent) { error("failed to alloc %zd bytes\n", sizeof(*ent)); rc = -ENOMEM; goto out; } ent->tbl = (struct acpi_subtable16 *) buf; list_add_tail(&container->ents, &ent->list); if (readw(&ent->tbl->length) > length || !readw(&ent->tbl->length)) { error("failed to validate all SRAT entries\n"); rc = -EINVAL; goto out; } length -= readw(&ent->tbl->length); buf += readw(&ent->tbl->length); } out: if (rc < 0) { if (container) free_nfit_container(container); else free(nfit); container = NULL; } return container; } static struct srat_container *read_srat(int fd) { void *buf; int rc = 0; unsigned int length; struct srat *srat = NULL; struct srat_container *container = NULL; srat = read_table(fd, "SRAT"); if (!srat) return NULL; container = calloc(1, sizeof(*container)); if (!container) { error("failed to alloc %d bytes\n", srat->h.length); rc = -ENOMEM; goto out; } list_head_init(&container->ents); container->srat = srat; length = srat->h.length - sizeof(*srat); if (!length) { error("no sub-tables found in SRAT\n"); rc = -EINVAL; goto out; } buf = srat + 1; while (length) { struct srat_ent *ent = calloc(1, sizeof(*ent)); if (!ent) { error("failed to alloc %zd bytes\n", sizeof(*ent)); rc = -ENOMEM; goto out; } ent->tbl = (struct acpi_subtable8 *) buf; list_add_tail(&container->ents, &ent->list); if (readb(&ent->tbl->length) > length || !readb(&ent->tbl->length)) { error("failed to validate all SRAT entries\n"); rc = -EINVAL; goto out; } length -= readb(&ent->tbl->length); buf += readb(&ent->tbl->length); } out: if (rc < 0) { if (container) free_srat_container(container); else free(srat); container = NULL; } return container; } enum acpi_table { ACPI_SRAT, ACPI_SLIT, ACPI_NFIT, ACPI_TABLES, }; static const char *acpi_table_name(enum acpi_table id) { const char *names[ACPI_TABLES] = { [ACPI_SRAT] = "srat", [ACPI_SLIT] = "slit", [ACPI_NFIT] = "nfit", }; return names[id]; } struct parameters { char *table[ACPI_TABLES]; char *new_table[ACPI_TABLES]; int in_fd[ACPI_TABLES]; int out_fd[ACPI_TABLES]; int nodes; int pxm; const char *path; } param = { .nodes = 2, }; struct split_context { uint64_t address; uint64_t length; int max_pxm; int max_region_id; int max_range_index; }; static int create_nfit(struct parameters *p, struct nfit_container *container, struct list_head *mems) { unsigned int oem_revision; size_t orig_size, size; struct nfit_ent *ent; struct nfit *nfit; void *buf; int rc; orig_size = readl(&container->nfit->h.length); size = orig_size; list_for_each(mems, ent, list) size += readw(&ent->tbl->length); buf = calloc(1, size); if (!buf) return -ENOMEM; nfit = buf; memcpy(nfit, container->nfit, sizeof(*nfit)); writel(size, &nfit->h.length); oem_revision = readl(&nfit->h.oem_revision); writel(oem_revision + 1, &nfit->h.oem_revision); buf += sizeof(*nfit); list_append_list(&container->ents, mems); list_for_each(&container->ents, ent, list) { memcpy(buf, ent->tbl, readw(&ent->tbl->length)); buf += readw(&ent->tbl->length); } writeb(acpi_checksum(nfit, size), &nfit->h.checksum); rc = write(p->out_fd[ACPI_NFIT], nfit, size); free(nfit); if (rc < 0) return -errno; return 0; } static int create_srat(struct parameters *p, struct srat_container *container, struct list_head *mems) { unsigned int oem_revision; size_t orig_size, size; struct srat_ent *ent; struct srat *srat; void *buf; int rc; orig_size = readl(&container->srat->h.length); size = orig_size; list_for_each(mems, ent, list) size += readb(&ent->tbl->length); buf = calloc(1, size); if (!buf) return -ENOMEM; srat = buf; memcpy(srat, container->srat, sizeof(*srat)); writel(size, &srat->h.length); oem_revision = readl(&srat->h.oem_revision); writel(oem_revision + 1, &srat->h.oem_revision); buf += sizeof(*srat); list_append_list(&container->ents, mems); list_for_each(&container->ents, ent, list) { memcpy(buf, ent->tbl, readb(&ent->tbl->length)); buf += readb(&ent->tbl->length); } writeb(acpi_checksum(srat, size), &srat->h.checksum); rc = write(p->out_fd[ACPI_SRAT], srat, size); free(srat); if (rc < 0) return -errno; return 0; } #define dbg(fmt, ...) \ ({if (verbose) { \ fprintf(stderr, fmt, ##__VA_ARGS__); \ } else { \ do { } while (0); \ }}) static int split_srat(struct parameters *p, struct split_context *split) { struct srat_container *srat = read_srat(p->in_fd[ACPI_SRAT]); struct srat_ent *ent, *found_ent = NULL; int count = 0, max_pxm = 0, i, rc; uint64_t length, address; struct srat_mem *m; LIST_HEAD(mems); list_for_each(&srat->ents, ent, list) { struct srat_generic *g; struct srat_cpu *c; int pxm, type; type = readb(&ent->tbl->type); switch (type) { case ACPI_SRAT_TYPE_MEMORY_AFFINITY: m = (struct srat_mem *) ent->tbl; pxm = readl(&m->proximity_domain); break; case ACPI_SRAT_TYPE_CPU_AFFINITY: c = (struct srat_cpu *) ent->tbl; pxm = readb(&c->proximity_domain_lo); pxm |= readw(&c->proximity_domain_hi[0]) << 8; pxm |= readb(&c->proximity_domain_hi[2]) << 24; break; case ACPI_SRAT_TYPE_GENERIC_AFFINITY: g = (struct srat_generic *) ent->tbl; pxm = readl(&g->proximity_domain); break; default: pxm = -1; break; } max_pxm = max(pxm, max_pxm); if (type != ACPI_SRAT_TYPE_MEMORY_AFFINITY) continue; if (p->pxm == pxm) { found_ent = ent; count++; } if (count > 1) { error("SRAT: no support for splitting multiple entry proximity domains\n"); return -ENXIO; } } if (!found_ent) { error("SRAT: proximity domain to split not found\n"); free_srat_container(srat); return -ENOENT; } ent = found_ent; m = (struct srat_mem *) ent->tbl; address = readq(&m->spa_base); length = readq(&m->spa_length); *split = (struct split_context) { .address = address, .length = length, .max_pxm = max_pxm, }; length /= p->nodes; writeq(length, &m->spa_length); dbg("SRAT: edit: %#llx@%#llx pxm: %d\n", (unsigned long long) length, (unsigned long long) address, p->pxm); address += length; for (i = 0; i < p->nodes - 1; i++) { struct srat_mem *srat_mem = calloc(1, sizeof(*srat_mem)); if (!srat_mem) { error("failed to alloc srat entry\n"); return -ENOMEM; } ent = calloc(1, sizeof(*ent)); if (!ent) { error("failed to alloc srat entry\n"); free(srat_mem); return -ENOMEM; } ent->tbl = (struct acpi_subtable8 *) srat_mem; writeb(ACPI_SRAT_TYPE_MEMORY_AFFINITY, &srat_mem->type); writeb(sizeof(*srat_mem), &srat_mem->length); writel(max_pxm + 1 + i, &srat_mem->proximity_domain); writeq(address, &srat_mem->spa_base); writeq(length, &srat_mem->spa_length); srat_mem->flags = m->flags; dbg("SRAT: add: %#llx@%#llx pxm: %d\n", (unsigned long long) length, (unsigned long long) address, max_pxm + 1 + i); address += length; list_add_tail(&mems, &ent->list); } rc = create_srat(p, srat, &mems); free_srat_container(srat); if (rc < 0) return rc; return max_pxm; } static int split_slit(struct parameters *p, struct split_context *split) { unsigned int oem_revision; int max_pxm = split->max_pxm; int nodes = max_pxm + p->nodes; struct slit *slit, *slit_old; int old_nodes, rc, i, j; size_t size; size = sizeof(*slit) + nodes * nodes; slit = calloc(1, size); if (!slit) { error("failed to allocated %zd bytes\n", size); return -ENOMEM; } slit_old = read_table(p->in_fd[ACPI_SLIT], "SLIT"); if (!slit_old) { error("failed to read SLIT\n"); free(slit); return -ENOMEM; } *slit = *slit_old; old_nodes = readq(&slit_old->count); writeq(nodes, &slit->count); writel(size, &slit->h.length); oem_revision = readl(&slit->h.oem_revision); writel(oem_revision + 1, &slit->h.oem_revision); for (i = 0; i < nodes; i++) for (j = 0; j < nodes; j++) { u8 val = 10; if (i > max_pxm && j > max_pxm) val = 10; else if (i <= max_pxm && j <= max_pxm) val = slit_old->entry[i * old_nodes + j]; else if (i > max_pxm) val = slit_old->entry[p->pxm * old_nodes + j]; else if (j > max_pxm) val = slit_old->entry[i * old_nodes + p->pxm]; /* * Linux requires distance 10 for the i == j * case and rejects distance 10 rejects the SLIT * if 10 is found anywhere else. Fixup val per * these constraints. */ if (val == 10 && i != j) val = 11; slit->entry[i * nodes + j] = val; } writeb(acpi_checksum(slit, size), &slit->h.checksum); rc = write(p->out_fd[ACPI_SLIT], slit, size); free(slit); free(slit_old); return rc; } static int split_nfit_map(struct parameters *p, struct nfit_map *map, struct list_head *maps, struct split_context *split) { int rc, i, max_region_id = split->max_region_id, max_range_index = split->max_range_index; uint64_t region_offset, region_size; struct nfit_ent *ent, *_ent; region_offset = readq(&map->region_offset); region_size = readq(&map->region_size); region_size /= p->nodes; writeq(region_size, &map->region_size); dbg("NFIT: edit: %#llx@%#llx region_id: %d\n", (unsigned long long) region_size, (unsigned long long) region_offset, readw(&map->region_id)); region_offset += region_size; for (i = 0; i < p->nodes - 1; i++) { struct nfit_map *nfit_map = calloc(1, sizeof(*nfit_map)); if (!nfit_map) { error("failed to alloc nfit entry\n"); rc = -ENOMEM; break; } ent = calloc(1, sizeof(*ent)); if (!ent) { error("failed to alloc nfit entry\n"); free(nfit_map); rc = -ENOMEM; break; } ent->tbl = (struct acpi_subtable16 *) nfit_map; *nfit_map = *map; writew(max_region_id + 1 + i, &nfit_map->region_id); writew(max_range_index + 1 + i, &nfit_map->range_index); writeq(region_size, &nfit_map->region_size); writeq(region_offset, &nfit_map->region_offset); dbg("NFIT: add: %#llx@%#llx region_id: %d\n", (unsigned long long) region_size, (unsigned long long) region_offset, max_region_id + 1 + i); region_offset += region_size; list_add_tail(maps, &ent->list); } if (i < p->nodes - 1) list_for_each_safe(maps, ent, _ent, list) { list_del(&ent->list); free(ent->tbl); free(ent); return rc; } split->max_region_id = max_region_id + i; return 0; } static int split_nfit(struct parameters *p, struct split_context *split) { int count = 0, max_pxm = split->max_pxm, i, rc, max_range_index = 0, max_region_id = 0; struct nfit_container *nfit = read_nfit(p->in_fd[ACPI_NFIT]); struct nfit_ent *ent, *_ent, *found_ent = NULL; uint64_t length, address; struct nfit_spa *spa; struct nfit_map *map; LIST_HEAD(new_maps); LIST_HEAD(mems); LIST_HEAD(maps); list_for_each(&nfit->ents, ent, list) { int pxm, type, range_index, region_id; type = readw(&ent->tbl->type); if (type == ACPI_NFIT_TYPE_MEMORY_MAP) { map = (struct nfit_map *) ent->tbl; region_id = readw(&map->region_id); max_region_id = max(max_region_id, region_id); continue; } if (type != ACPI_NFIT_TYPE_SYSTEM_ADDRESS) continue; spa = (struct nfit_spa *) ent->tbl; range_index = readw(&spa->range_index); max_range_index = max(range_index, max_range_index); if (memcmp(&spa->type_uuid, &uuid_pmem, sizeof(uuid_pmem)) != 0) continue; pxm = readl(&spa->proximity_domain); if (pxm != p->pxm) continue; if (split->address != readq(&spa->spa_base)) continue; if (split->length != readq(&spa->spa_length)) continue; found_ent = ent; count++; if (count > 1) { error("NFIT: no support for splitting multiple entry proximity domains\n"); return -ENXIO; } } if (!found_ent) { dbg("NFIT: proximity domain to split not found\n"); free_nfit_container(nfit); return -ENOENT; } ent = found_ent; spa = (struct nfit_spa *) ent->tbl; address = readq(&spa->spa_base); length = readq(&spa->spa_length) / p->nodes; writeq(length, &spa->spa_length); dbg("NFIT: edit: %#llx@%#llx pxm: %d\n", (unsigned long long) length, (unsigned long long) address, p->pxm); address += length; for (i = 0; i < p->nodes - 1; i++) { struct nfit_spa *nfit_spa = calloc(1, sizeof(*nfit_spa)); if (!nfit_spa) { error("failed to alloc nfit entry\n"); rc = -ENOMEM; break; } ent = calloc(1, sizeof(*ent)); if (!ent) { error("failed to alloc nfit entry\n"); free(nfit_spa); rc = -ENOMEM; break; } ent->tbl = (struct acpi_subtable16 *) nfit_spa; *nfit_spa = *spa; writew(max_range_index + i + 1, &nfit_spa->range_index); writel(max_pxm + 1 + i, &nfit_spa->proximity_domain); writeq(address, &nfit_spa->spa_base); writeq(length, &nfit_spa->spa_length); dbg("NFIT: add: %#llx@%#llx pxm: %d\n", (unsigned long long) length, (unsigned long long) address, max_pxm + 1 + i); address += length; list_add_tail(&mems, &ent->list); } if (i < p->nodes - 1) list_for_each_safe(&mems, ent, _ent, list) { list_del(&ent->list); free(ent->tbl); free(ent); return rc; } /* * Find and split the maps that might be referring to split * address range. */ split->max_region_id = max_region_id; split->max_range_index = max_range_index; list_for_each_safe(&nfit->ents, ent, _ent, list) { unsigned int type; type = readw(&ent->tbl->type); if (type != ACPI_NFIT_TYPE_MEMORY_MAP) continue; map = (struct nfit_map *) ent->tbl; if (map->range_index != spa->range_index) continue; list_del_from(&nfit->ents, &ent->list); list_add_tail(&maps, &ent->list); } list_for_each(&maps, ent, list) { map = (struct nfit_map *) ent->tbl; rc = split_nfit_map(p, map, &new_maps, split); if (rc) return rc; } list_append_list(&maps, &new_maps); list_append_list(&mems, &maps); rc = create_nfit(p, nfit, &mems); free_nfit_container(nfit); if (rc < 0) return rc; return max_pxm; } static int do_split(struct parameters *p) { struct split_context split; int rc = split_srat(p, &split); if (rc < 0) return rc; fprintf(stderr, "created: %s\n", p->new_table[ACPI_SRAT]); rc = split_slit(p, &split); if (rc < 0) return rc; fprintf(stderr, "created: %s\n", p->new_table[ACPI_SLIT]); rc = split_nfit(p, &split); if (rc == -ENOENT) { unlink(p->new_table[ACPI_NFIT]); return 0; } if (rc < 0) return rc; fprintf(stderr, "created: %s\n", p->new_table[ACPI_NFIT]); return 0; } int cmd_split_acpi(int argc, const char **argv, void *ctx) { int i, rc = 0; const char * const u[] = { "daxctl split-acpi ", NULL }; const struct option options[] = { OPT_STRING('d', "directory", ¶m.path, "path", "Path to ACPI tables dumped by \"acpixtract -a\""), OPT_INTEGER('p', "pxm", ¶m.pxm, "Proximity domain to split"), OPT_INTEGER('n', "nodes", ¶m.nodes, "Number of nodes to split capacity (default 2)"), OPT_BOOLEAN('v', "verbose", &verbose, "Enable verbose output"), OPT_END(), }; argc = parse_options(argc, argv, options, u, 0); for (i = 0; i < argc; i++) { error("unknown parameter \"%s\"\n", argv[i]); rc = -EINVAL; } if (param.nodes < 2) { error("--nodes=%d, must be greater than 2\n", param.nodes); rc = -EINVAL; } if (!is_power_of_2(param.nodes)) { error("--nodes=%d, must be power of 2\n", param.nodes); rc = -EINVAL; } if (rc) usage_with_options(u, options); for (i = 0; i < ACPI_TABLES; i++) { rc = asprintf(¶m.table[i], "%s/%s.dat", param.path ? param.path : ".", acpi_table_name(i)); if (rc < 0) { error("failed to allocate path for %s\n", acpi_table_name(i)); break; } rc = open(param.table[i], O_RDONLY); if (rc < 0 && i > ACPI_SLIT) { error("failed to open required %s\n", param.table[i]); break; } if (rc < 0) continue; param.in_fd[i] = rc; rc = asprintf(¶m.new_table[i], "%s/%s.dat.new", param.path ? param.path : ".", acpi_table_name(i)); if (rc < 0) { error("failed to allocate path for %s.new\n", acpi_table_name(i)); break; } rc = open(param.new_table[i], O_RDWR | O_TRUNC | O_CREAT, 0640); if (rc < 0 && i <= ACPI_SLIT) { error("failed to open %s\n", param.new_table[i]); break; } param.out_fd[i] = rc; } if (rc < 0) { rc = EXIT_FAILURE; goto out; } rc = do_split(¶m); out: for (i = 0; i < ACPI_TABLES; i++) { free(param.table[i]); free(param.new_table[i]); if (param.in_fd[i] > 0) close(param.in_fd[i]); if (param.out_fd[i] > 0) close(param.out_fd[i]); } return rc; } ndctl-81/daxctl/builtin.h000066400000000000000000000017021476737544500155340ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (C) 2015-2020 Intel Corporation. All rights reserved. */ #ifndef _DAXCTL_BUILTIN_H_ #define _DAXCTL_BUILTIN_H_ struct daxctl_ctx; int cmd_list(int argc, const char **argv, struct daxctl_ctx *ctx); int cmd_migrate(int argc, const char **argv, struct daxctl_ctx *ctx); int cmd_create_device(int argc, const char **argv, struct daxctl_ctx *ctx); int cmd_destroy_device(int argc, const char **argv, struct daxctl_ctx *ctx); int cmd_reconfig_device(int argc, const char **argv, struct daxctl_ctx *ctx); int cmd_disable_device(int argc, const char **argv, struct daxctl_ctx *ctx); int cmd_enable_device(int argc, const char **argv, struct daxctl_ctx *ctx); int cmd_online_memory(int argc, const char **argv, struct daxctl_ctx *ctx); int cmd_offline_memory(int argc, const char **argv, struct daxctl_ctx *ctx); int cmd_split_acpi(int argc, const char **argv, struct daxctl_ctx *ctx); #endif /* _DAXCTL_BUILTIN_H_ */ ndctl-81/daxctl/daxctl.c000066400000000000000000000053761476737544500153530ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2015-2020 Intel Corporation. All rights reserved. // Copyright (C) 2005 Andreas Ericsson. All rights reserved. /* originally copied from perf and git */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include const char daxctl_usage_string[] = "daxctl [--version] [--help] COMMAND [ARGS]"; const char daxctl_more_info_string[] = "See 'daxctl help COMMAND' for more information on a specific command.\n" " daxctl --list-cmds to see all available commands"; static int cmd_version(int argc, const char **argv, struct daxctl_ctx *ctx) { printf("%s\n", VERSION); return 0; } static int cmd_help(int argc, const char **argv, struct daxctl_ctx *ctx) { const char * const builtin_help_subcommands[] = { "list", NULL, }; struct option builtin_help_options[] = { OPT_END(), }; const char *builtin_help_usage[] = { "daxctl help [command]", NULL }; argc = parse_options_subcommand(argc, argv, builtin_help_options, builtin_help_subcommands, builtin_help_usage, 0); if (!argv[0]) { printf("\n usage: %s\n\n", daxctl_usage_string); printf("\n %s\n\n", daxctl_more_info_string); return 0; } return help_show_man_page(argv[0], "daxctl", "DAXCTL_MAN_VIEWER"); } static struct cmd_struct commands[] = { { "version", .d_fn = cmd_version }, { "list", .d_fn = cmd_list }, { "help", .d_fn = cmd_help }, { "split-acpi", .d_fn = cmd_split_acpi, }, { "migrate-device-model", .d_fn = cmd_migrate }, { "create-device", .d_fn = cmd_create_device }, { "destroy-device", .d_fn = cmd_destroy_device }, { "reconfigure-device", .d_fn = cmd_reconfig_device }, { "online-memory", .d_fn = cmd_online_memory }, { "offline-memory", .d_fn = cmd_offline_memory }, { "disable-device", .d_fn = cmd_disable_device }, { "enable-device", .d_fn = cmd_enable_device }, }; int main(int argc, const char **argv) { struct daxctl_ctx *ctx; int rc; /* Look for flags.. */ argv++; argc--; main_handle_options(&argv, &argc, daxctl_usage_string, commands, ARRAY_SIZE(commands)); if (argc > 0) { if (!prefixcmp(argv[0], "--")) argv[0] += 2; } else { /* The user didn't specify a command; give them help */ printf("\n usage: %s\n\n", daxctl_usage_string); printf("\n %s\n\n", daxctl_more_info_string); goto out; } rc = daxctl_new(&ctx); if (rc) goto out; main_handle_internal_command(argc, argv, ctx, commands, ARRAY_SIZE(commands), PROG_DAXCTL); daxctl_unref(ctx); fprintf(stderr, "Unknown command: '%s'\n", argv[0]); out: return 1; } ndctl-81/daxctl/daxctl.example.conf000066400000000000000000000021311476737544500174720ustar00rootroot00000000000000# This is an example config file for daxctl # daxctl supports multiple configuration files. All files with the # .conf suffix under {sysconfdir}/daxctl.conf.d/ are valid config files. # Lines beginning with a '#' are treated as comments and ignored # The (section-name, subsection-name) tuple must be unique across all # config files. # The following example config snippet is used to automatically reconfigure # an nvdimm namespace with the specified UUID from 'devdax' mode to # 'system-ram'. # Uncomment the lines to activate it, and substitute the correct UUIDs and # other parameters for the desired behavior. # This can be replicated as many times as necessary to handle multiple # namespaces/dax devices, so long as the subsection name (e.g. # "unique_identifier_foo" in the example below) is unique across all # sections and all config files in the config path. # The nvdimm.uuid can be obtained from a command such as: # "ndctl list --device-dax" # [reconfigure-device unique_identifier_foo] # nvdimm.uuid=ed93e918-e165-49d8-921d-383d7b9660c5 # mode = system-ram # online = true # movable = false ndctl-81/daxctl/daxdev-reconfigure@.service000066400000000000000000000003351476737544500211610ustar00rootroot00000000000000[Unit] Description=Automatic daxctl device reconfiguration Documentation=man:daxctl-reconfigure-device(1) [Service] Type=forking GuessMainPID=false ExecStart=/bin/sh -c "exec daxctl reconfigure-device --check-config %I" ndctl-81/daxctl/device.c000066400000000000000000000676601476737544500153370ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) 2019-2020 Intel Corporation. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "filter.h" #include "json.h" static struct { const char *dev; const char *mode; const char *region; const char *size; const char *align; const char *input; bool check_config; bool no_online; bool no_movable; bool force; bool human; bool verbose; } param; enum dev_mode { DAXCTL_DEV_MODE_UNKNOWN, DAXCTL_DEV_MODE_DEVDAX, DAXCTL_DEV_MODE_RAM, }; struct mapping { unsigned long long start, end, pgoff; }; static enum dev_mode reconfig_mode = DAXCTL_DEV_MODE_UNKNOWN; static long long align = -1; static long long size = -1; static unsigned long flags; static struct mapping *maps = NULL; static long long nmaps = -1; enum device_action { ACTION_RECONFIG, ACTION_ONLINE, ACTION_OFFLINE, ACTION_CREATE, ACTION_DISABLE, ACTION_ENABLE, ACTION_DESTROY, }; #define CONF_SECTION "reconfigure-device" #define CONF_NVDIMM_UUID_STR "nvdimm.uuid" #define BASE_OPTIONS() \ OPT_STRING('r', "region", ¶m.region, "region-id", "filter by region"), \ OPT_BOOLEAN('u', "human", ¶m.human, "use human friendly number formats"), \ OPT_BOOLEAN('v', "verbose", ¶m.verbose, "emit more debug messages") #define RECONFIG_OPTIONS() \ OPT_STRING('m', "mode", ¶m.mode, "mode", "mode to switch the device to"), \ OPT_BOOLEAN('N', "no-online", ¶m.no_online, \ "don't auto-online memory sections"), \ OPT_BOOLEAN('f', "force", ¶m.force, \ "attempt to offline memory sections before reconfiguration"), \ OPT_BOOLEAN('C', "check-config", ¶m.check_config, \ "use config files to determine parameters for the operation") #define CREATE_OPTIONS() \ OPT_STRING('s', "size", ¶m.size, "size", "size to switch the device to"), \ OPT_STRING('a', "align", ¶m.align, "align", "alignment to switch the device to"), \ OPT_STRING('\0', "input", ¶m.input, "input", "input device JSON file") #define DESTROY_OPTIONS() \ OPT_BOOLEAN('f', "force", ¶m.force, \ "attempt to disable before destroying device") #define ZONE_OPTIONS() \ OPT_BOOLEAN('\0', "no-movable", ¶m.no_movable, \ "online memory in ZONE_NORMAL") static const struct option create_options[] = { BASE_OPTIONS(), CREATE_OPTIONS(), OPT_END(), }; static const struct option reconfig_options[] = { BASE_OPTIONS(), CREATE_OPTIONS(), RECONFIG_OPTIONS(), ZONE_OPTIONS(), OPT_END(), }; static const struct option online_options[] = { BASE_OPTIONS(), ZONE_OPTIONS(), OPT_END(), }; static const struct option offline_options[] = { BASE_OPTIONS(), OPT_END(), }; static const struct option disable_options[] = { BASE_OPTIONS(), OPT_END(), }; static const struct option enable_options[] = { BASE_OPTIONS(), OPT_END(), }; static const struct option destroy_options[] = { BASE_OPTIONS(), DESTROY_OPTIONS(), OPT_END(), }; static int sort_mappings(const void *a, const void *b) { json_object **jsoa, **jsob; struct json_object *va, *vb; unsigned long long pga, pgb; jsoa = (json_object **)a; jsob = (json_object **)b; if (!*jsoa && !*jsob) return 0; if (!json_object_object_get_ex(*jsoa, "page_offset", &va) || !json_object_object_get_ex(*jsob, "page_offset", &vb)) return 0; pga = json_object_get_int64(va); pgb = json_object_get_int64(vb); return pga > pgb; } static int parse_device_file(const char *filename) { struct json_object *jobj, *jval = NULL, *jmappings = NULL; int i, rc = -EINVAL, region_id, id; const char *chardev; char *region = NULL; jobj = json_object_from_file(filename); if (!jobj) return rc; if (!json_object_object_get_ex(jobj, "align", &jval)) return rc; param.align = json_object_get_string(jval); if (!json_object_object_get_ex(jobj, "size", &jval)) return rc; param.size = json_object_get_string(jval); if (!json_object_object_get_ex(jobj, "chardev", &jval)) return rc; chardev = json_object_get_string(jval); if (sscanf(chardev, "dax%u.%u", ®ion_id, &id) != 2) return rc; if (asprintf(®ion, "%u", region_id) < 0) return rc; param.region = region; if (!json_object_object_get_ex(jobj, "mappings", &jmappings)) return rc; json_object_array_sort(jmappings, sort_mappings); nmaps = json_object_array_length(jmappings); maps = calloc(nmaps, sizeof(*maps)); if (!maps) return -ENOMEM; for (i = 0; i < nmaps; i++) { struct json_object *j, *val; j = json_object_array_get_idx(jmappings, i); if (!j) goto err; if (!json_object_object_get_ex(j, "start", &val)) goto err; maps[i].start = json_object_get_int64(val); if (!json_object_object_get_ex(j, "end", &val)) goto err; maps[i].end = json_object_get_int64(val); if (!json_object_object_get_ex(j, "page_offset", &val)) goto err; maps[i].pgoff = json_object_get_int64(val); } return 0; err: free(maps); return rc; } static int conf_string_to_bool(const char *str) { if (!str) return INT_MAX; if (strncmp(str, "t", 1) == 0 || strncmp(str, "T", 1) == 0 || strncmp(str, "y", 1) == 0 || strncmp(str, "Y", 1) == 0 || strncmp(str, "1", 1) == 0) return true; if (strncmp(str, "f", 1) == 0 || strncmp(str, "F", 1) == 0 || strncmp(str, "n", 1) == 0 || strncmp(str, "N", 1) == 0 || strncmp(str, "0", 1) == 0) return false; return INT_MAX; } #define conf_assign_inverted_bool(p, conf_var) \ do { \ if (conf_string_to_bool(conf_var) != INT_MAX) \ param.p = !conf_string_to_bool(conf_var); \ } while(0) static int parse_config_reconfig_set_params(struct daxctl_ctx *ctx, const char *device, const char *uuid) { const char *conf_online = NULL, *conf_movable = NULL; const struct config configs[] = { CONF_SEARCH(CONF_SECTION, CONF_NVDIMM_UUID_STR, uuid, "mode", ¶m.mode, NULL), CONF_SEARCH(CONF_SECTION, CONF_NVDIMM_UUID_STR, uuid, "online", &conf_online, NULL), CONF_SEARCH(CONF_SECTION, CONF_NVDIMM_UUID_STR, uuid, "movable", &conf_movable, NULL), CONF_END(), }; const char *prefix = "./", *daxctl_configs; int rc; daxctl_configs = daxctl_get_config_path(ctx); if (daxctl_configs == NULL) return 0; rc = parse_configs_prefix(daxctl_configs, prefix, configs); if (rc < 0) return rc; conf_assign_inverted_bool(no_online, conf_online); conf_assign_inverted_bool(no_movable, conf_movable); return 0; } static bool daxctl_ndns_has_device(struct ndctl_namespace *ndns, const char *device) { struct daxctl_region *dax_region; struct ndctl_dax *dax; dax = ndctl_namespace_get_dax(ndns); if (!dax) return false; dax_region = ndctl_dax_get_daxctl_region(dax); if (dax_region) { struct daxctl_dev *dev; dev = daxctl_dev_get_first(dax_region); if (dev) { if (strcmp(daxctl_dev_get_devname(dev), device) == 0) return true; } } return false; } static int parse_config_reconfig(struct daxctl_ctx *ctx, const char *device) { struct ndctl_namespace *ndns; struct ndctl_ctx *ndctl_ctx; struct ndctl_region *region; struct ndctl_bus *bus; struct ndctl_dax *dax; int rc, found = 0; char uuid_buf[40]; uuid_t uuid; if (strcmp(device, "all") == 0) return 0; rc = ndctl_new(&ndctl_ctx); if (rc < 0) return rc; ndctl_bus_foreach(ndctl_ctx, bus) { ndctl_region_foreach(bus, region) { ndctl_namespace_foreach(region, ndns) { if (daxctl_ndns_has_device(ndns, device)) { dax = ndctl_namespace_get_dax(ndns); if (!dax) continue; ndctl_dax_get_uuid(dax, uuid); found = 1; } } } } if (!found) { fprintf(stderr, "no UUID match for %s found in config files\n", device); return 0; } uuid_unparse(uuid, uuid_buf); return parse_config_reconfig_set_params(ctx, device, uuid_buf); } static int parse_device_config(struct daxctl_ctx *ctx, const char *device, enum device_action action) { switch (action) { case ACTION_RECONFIG: return parse_config_reconfig(ctx, device); default: return 0; } } static const char *parse_device_options(int argc, const char **argv, enum device_action action, const struct option *options, const char *usage, struct daxctl_ctx *ctx) { const char * const u[] = { usage, NULL }; unsigned long long units = 1; int i, rc = 0; char *device = NULL; argc = parse_options(argc, argv, options, u, 0); if (argc > 0) device = basename(argv[0]); /* Handle action-agnostic non-option arguments */ if (argc == 0 && action != ACTION_CREATE) { char *action_string; switch (action) { case ACTION_RECONFIG: action_string = "reconfigure"; break; case ACTION_ONLINE: action_string = "online memory for"; break; case ACTION_OFFLINE: action_string = "offline memory for"; break; case ACTION_DISABLE: action_string = "disable"; break; case ACTION_ENABLE: action_string = "enable"; break; case ACTION_DESTROY: action_string = "destroy"; break; default: action_string = "<>"; break; } fprintf(stderr, "specify a device to %s, or \"all\"\n", action_string); rc = -EINVAL; } /* ACTION_CREATE expects 0 parameters */ i = action == ACTION_CREATE ? 0 : 1; for (; i < argc; i++) { fprintf(stderr, "unknown extra parameter \"%s\"\n", argv[i]); rc = -EINVAL; } if (rc) { usage_with_options(u, options); return NULL; } /* Handle action-agnostic options */ if (param.verbose) daxctl_set_log_priority(ctx, LOG_DEBUG); if (param.human) flags |= UTIL_JSON_HUMAN; /* Scan config file(s) for options. This sets param.foo accordingly */ if (device && param.check_config) { if (param.mode || param.no_online || param.no_movable) { fprintf(stderr, "%s: -C cannot be used with --mode, --(no-)movable, or --(no-)online\n", device); usage_with_options(u, options); } rc = parse_device_config(ctx, device, action); if (rc) { fprintf(stderr, "error parsing config file: %s\n", strerror(-rc)); return NULL; } if (!param.mode && !param.no_online && !param.no_movable) { fprintf(stderr, "%s: missing or malformed config section\n", device); /* * Exit with success since the most common case is there is * no config defined for this device, and we don't want to * treat that as an error. There isn't an easy way currently * to distinguish between a malformed config entry from a * completely missing config section. */ exit(0); } } /* Handle action-specific options */ switch (action) { case ACTION_RECONFIG: if (!param.size && !param.align && !param.mode) { fprintf(stderr, "error: a 'align', 'mode' or 'size' option is required\n"); usage_with_options(u, reconfig_options); rc = -EINVAL; } if (param.size || param.align) { if (param.size) size = __parse_size64(param.size, &units); if (param.align) align = __parse_size64(param.align, &units); } else if (strcmp(param.mode, "system-ram") == 0) { reconfig_mode = DAXCTL_DEV_MODE_RAM; } else if (strcmp(param.mode, "devdax") == 0) { reconfig_mode = DAXCTL_DEV_MODE_DEVDAX; if (param.no_online) { fprintf(stderr, "--no-online is incompatible with --mode=devdax\n"); rc = -EINVAL; } } break; case ACTION_CREATE: if (param.input && (rc = parse_device_file(param.input)) != 0) { fprintf(stderr, "error: failed to parse device file: %s\n", strerror(-rc)); break; } if (param.size) size = __parse_size64(param.size, &units); if (param.align) align = __parse_size64(param.align, &units); /* fall through */ case ACTION_ONLINE: case ACTION_DESTROY: case ACTION_OFFLINE: case ACTION_DISABLE: case ACTION_ENABLE: /* nothing special */ break; } if (rc) { usage_with_options(u, options); return NULL; } return device; } static int dev_online_memory(struct daxctl_dev *dev) { struct daxctl_memory *mem = daxctl_dev_get_memory(dev); const char *devname = daxctl_dev_get_devname(dev); int num_sections, num_on, rc; if (!mem) { fprintf(stderr, "%s: failed to get the memory object\n", devname); return -ENXIO; } /* get total number of sections and sections already online */ num_sections = daxctl_memory_num_sections(mem); if (num_sections < 0) { fprintf(stderr, "%s: failed to get number of memory sections\n", devname); return num_sections; } num_on = daxctl_memory_is_online(mem); if (num_on < 0) { fprintf(stderr, "%s: failed to determine online state: %s\n", devname, strerror(-num_on)); return num_on; } if (num_on) fprintf(stderr, "%s:\n WARNING: detected a race while onlining memory\n" " Some memory may not be in the expected zone. It is\n" " recommended to disable any other onlining mechanisms,\n" " and retry. If onlining is to be left to other agents,\n" " use the --no-online option to suppress this warning\n", devname); if (num_on == num_sections) { fprintf(stderr, "%s: all memory sections (%d) already online\n", devname, num_on); return 1; } if (num_on > 0) fprintf(stderr, "%s: %d memory section%s already online\n", devname, num_on, num_on == 1 ? "" : "s"); /* online the remaining sections */ if (param.no_movable) rc = daxctl_memory_online_no_movable(mem); else rc = daxctl_memory_online(mem); if (rc < 0) { fprintf(stderr, "%s: failed to online memory: %s\n", devname, strerror(-rc)); return rc; } if (param.verbose) fprintf(stderr, "%s: %d memory section%s onlined\n", devname, rc, rc == 1 ? "" : "s"); /* all sections should now be online */ num_on = daxctl_memory_is_online(mem); if (num_on < 0) { fprintf(stderr, "%s: failed to determine online state: %s\n", devname, strerror(-num_on)); return num_on; } if (num_on < num_sections) { fprintf(stderr, "%s: failed to online %d memory sections\n", devname, num_sections - num_on); return -ENXIO; } return 0; } static int dev_offline_memory(struct daxctl_dev *dev) { struct daxctl_memory *mem = daxctl_dev_get_memory(dev); const char *devname = daxctl_dev_get_devname(dev); int num_sections, num_on, num_off, rc; if (!mem) { fprintf(stderr, "%s: failed to get the memory object\n", devname); return -ENXIO; } /* get total number of sections and sections already offline */ num_sections = daxctl_memory_num_sections(mem); if (num_sections < 0) { fprintf(stderr, "%s: failed to get number of memory sections\n", devname); return num_sections; } num_on = daxctl_memory_is_online(mem); if (num_on < 0) { fprintf(stderr, "%s: failed to determine online state: %s\n", devname, strerror(-num_on)); return num_on; } num_off = num_sections - num_on; if (num_off == num_sections) { fprintf(stderr, "%s: all memory sections (%d) already offline\n", devname, num_off); return 1; } if (num_off) fprintf(stderr, "%s: %d memory section%s already offline\n", devname, num_off, num_off == 1 ? "" : "s"); /* offline the remaining sections */ rc = daxctl_memory_offline(mem); if (rc < 0) { fprintf(stderr, "%s: failed to offline memory: %s\n", devname, strerror(-rc)); return rc; } if (param.verbose) fprintf(stderr, "%s: %d memory section%s offlined\n", devname, rc, rc == 1 ? "" : "s"); /* all sections should now be ofline */ num_on = daxctl_memory_is_online(mem); if (num_on < 0) { fprintf(stderr, "%s: failed to determine online state: %s\n", devname, strerror(-num_on)); return num_on; } if (num_on) { fprintf(stderr, "%s: failed to offline %d memory sections\n", devname, num_on); return -ENXIO; } return 0; } static int dev_resize(struct daxctl_dev *dev, unsigned long long val) { int rc; rc = daxctl_dev_set_size(dev, val); if (rc < 0) return rc; return 0; } static int dev_destroy(struct daxctl_dev *dev) { const char *devname = daxctl_dev_get_devname(dev); int rc; if (daxctl_dev_is_enabled(dev) && !param.force) { fprintf(stderr, "%s is active, specify --force for deletion\n", devname); return -ENXIO; } else { rc = daxctl_dev_disable(dev); if (rc) { fprintf(stderr, "%s: disable failed: %s\n", daxctl_dev_get_devname(dev), strerror(-rc)); return rc; } } rc = daxctl_dev_set_size(dev, 0); if (rc < 0) return rc; rc = daxctl_region_destroy_dev(daxctl_dev_get_region(dev), dev); /* * The kernel treats daxX.0 specially. It can't be deleted to ensure * there is always a /sys/bus/dax/ present. If this happens, an * EBUSY is returned. Expect it and don't treat it as an error. */ if (daxctl_dev_get_id(dev) == 0 && rc == -EBUSY) return 0; if (rc < 0) return rc; return 0; } static int disable_devdax_device(struct daxctl_dev *dev) { struct daxctl_memory *mem = daxctl_dev_get_memory(dev); const char *devname = daxctl_dev_get_devname(dev); int rc; if (mem) { fprintf(stderr, "%s was already in system-ram mode\n", devname); return 1; } rc = daxctl_dev_disable(dev); if (rc) { fprintf(stderr, "%s: disable failed: %s\n", daxctl_dev_get_devname(dev), strerror(-rc)); return rc; } return 0; } static int reconfig_mode_system_ram(struct daxctl_dev *dev) { const char *devname = daxctl_dev_get_devname(dev); int rc, skip_enable = 0; if (param.no_online || !param.no_movable) { if (!param.force && daxctl_dev_will_auto_online_memory(dev)) { fprintf(stderr, "%s: error: kernel policy will auto-online memory, aborting\n", devname); return -EBUSY; } } if (daxctl_dev_is_enabled(dev)) { rc = disable_devdax_device(dev); if (rc < 0) return rc; if (rc > 0) skip_enable = 1; } if (!skip_enable) { rc = daxctl_dev_enable_ram(dev); if (rc) return rc; } if (param.no_online) return 0; return dev_online_memory(dev); } static int disable_system_ram_device(struct daxctl_dev *dev) { struct daxctl_memory *mem = daxctl_dev_get_memory(dev); const char *devname = daxctl_dev_get_devname(dev); int rc; if (!mem) { fprintf(stderr, "%s was already in devdax mode\n", devname); return 1; } if (param.force) { rc = dev_offline_memory(dev); if (rc < 0) return rc; } rc = daxctl_memory_is_online(mem); if (rc < 0) { fprintf(stderr, "%s: failed to determine online state: %s\n", devname, strerror(-rc)); return rc; } if (rc > 0) { if (param.verbose) { fprintf(stderr, "%s: found %d memory sections online\n", devname, rc); fprintf(stderr, "%s: refusing to change modes\n", devname); } return -EBUSY; } rc = daxctl_dev_disable(dev); if (rc) { fprintf(stderr, "%s: disable failed: %s\n", daxctl_dev_get_devname(dev), strerror(-rc)); return rc; } return 0; } static int reconfig_mode_devdax(struct daxctl_dev *dev) { int rc; if (daxctl_dev_is_enabled(dev)) { rc = disable_system_ram_device(dev); if (rc) return rc; } rc = daxctl_dev_enable_devdax(dev); if (rc) return rc; return 0; } static int do_create(struct daxctl_region *region, long long val, struct json_object **jdevs) { struct json_object *jdev; struct daxctl_dev *dev; int i, rc = 0; long long alloc = 0; if (daxctl_region_create_dev(region)) return -ENOSPC; dev = daxctl_region_get_dev_seed(region); if (!dev) return -ENOSPC; if (val == -1) val = daxctl_region_get_available_size(region); if (val <= 0) return -ENOSPC; if (align > 0) { rc = daxctl_dev_set_align(dev, align); if (rc < 0) return rc; } /* @maps is ordered by page_offset */ for (i = 0; i < nmaps; i++) { rc = daxctl_dev_set_mapping(dev, maps[i].start, maps[i].end); if (rc < 0) return rc; alloc += (maps[i].end - maps[i].start + 1); } if (nmaps > 0 && val > 0 && alloc != val) { fprintf(stderr, "%s: allocated %lld but specified size %lld\n", daxctl_dev_get_devname(dev), alloc, val); } else { rc = daxctl_dev_set_size(dev, val); if (rc < 0) return rc; } rc = daxctl_dev_enable_devdax(dev); if (rc) { fprintf(stderr, "%s: enable failed: %s\n", daxctl_dev_get_devname(dev), strerror(-rc)); return rc; } *jdevs = json_object_new_array(); if (*jdevs) { jdev = util_daxctl_dev_to_json(dev, flags); if (jdev) json_object_array_add(*jdevs, jdev); } return 0; } static int do_reconfig(struct daxctl_dev *dev, enum dev_mode mode, struct json_object **jdevs) { const char *devname = daxctl_dev_get_devname(dev); struct json_object *jdev; int rc = 0; if (align > 0) { rc = daxctl_dev_set_align(dev, align); if (rc < 0) return rc; } if (size >= 0) { rc = dev_resize(dev, size); return rc; } switch (mode) { case DAXCTL_DEV_MODE_RAM: rc = reconfig_mode_system_ram(dev); break; case DAXCTL_DEV_MODE_DEVDAX: rc = reconfig_mode_devdax(dev); break; default: fprintf(stderr, "%s: unknown mode requested: %d\n", devname, mode); rc = -EINVAL; } if (rc < 0) return rc; *jdevs = json_object_new_array(); if (*jdevs) { jdev = util_daxctl_dev_to_json(dev, flags); if (jdev) json_object_array_add(*jdevs, jdev); } return 0; } static int do_xline(struct daxctl_dev *dev, enum device_action action) { struct daxctl_memory *mem = daxctl_dev_get_memory(dev); const char *devname = daxctl_dev_get_devname(dev); int rc; if (!mem) { fprintf(stderr, "%s: memory operations are not applicable in devdax mode\n", devname); return -ENXIO; } switch (action) { case ACTION_ONLINE: rc = dev_online_memory(dev); break; case ACTION_OFFLINE: rc = dev_offline_memory(dev); break; default: fprintf(stderr, "%s: invalid action: %d\n", devname, action); rc = -EINVAL; } return rc; } static int do_xble(struct daxctl_dev *dev, enum device_action action) { struct daxctl_memory *mem = daxctl_dev_get_memory(dev); const char *devname = daxctl_dev_get_devname(dev); int rc; if (mem) { fprintf(stderr, "%s: status operations are only applicable in devdax mode\n", devname); return -ENXIO; } switch (action) { case ACTION_ENABLE: rc = daxctl_dev_enable_devdax(dev); if (rc) { fprintf(stderr, "%s: enable failed: %s\n", daxctl_dev_get_devname(dev), strerror(-rc)); return rc; } break; case ACTION_DISABLE: rc = daxctl_dev_disable(dev); if (rc) { fprintf(stderr, "%s: disable failed: %s\n", daxctl_dev_get_devname(dev), strerror(-rc)); return rc; } break; default: fprintf(stderr, "%s: invalid action: %d\n", devname, action); rc = -EINVAL; } return rc; } static int do_xaction_region(enum device_action action, struct daxctl_ctx *ctx, int *processed) { struct json_object *jdevs = NULL; struct daxctl_region *region; int rc = -ENXIO; *processed = 0; daxctl_region_foreach(ctx, region) { if (!util_daxctl_region_filter(region, param.region)) continue; switch (action) { case ACTION_CREATE: rc = do_create(region, size, &jdevs); if (rc == 0) (*processed)++; break; default: rc = -EINVAL; break; } } free(maps); /* * jdevs is the containing json array for all devices we are reporting * on. It therefore needs to be outside the region/device iterators, * and passed in to the do_ functions to add their objects to */ if (jdevs) util_display_json_array(stdout, jdevs, flags); return rc; } static int do_xaction_device(const char *device, enum device_action action, struct daxctl_ctx *ctx, int *processed) { struct json_object *jdevs = NULL; struct daxctl_region *region; struct daxctl_dev *dev; int rc = -ENXIO, saved_rc = 0; *processed = 0; daxctl_region_foreach(ctx, region) { if (!util_daxctl_region_filter(region, param.region)) continue; daxctl_dev_foreach(region, dev) { if (!util_daxctl_dev_filter(dev, device)) continue; switch (action) { case ACTION_RECONFIG: rc = do_reconfig(dev, reconfig_mode, &jdevs); if (rc == 0) (*processed)++; break; case ACTION_ONLINE: rc = do_xline(dev, action); if (rc == 0) (*processed)++; break; case ACTION_OFFLINE: rc = do_xline(dev, action); if (rc == 0) (*processed)++; break; case ACTION_ENABLE: rc = do_xble(dev, action); if (rc == 0) (*processed)++; break; case ACTION_DISABLE: rc = do_xble(dev, action); if (rc == 0) (*processed)++; break; case ACTION_DESTROY: rc = dev_destroy(dev); if (rc == 0) (*processed)++; break; default: rc = -EINVAL; break; } if (rc) saved_rc = rc; } } /* * jdevs is the containing json array for all devices we are reporting * on. It therefore needs to be outside the region/device iterators, * and passed in to the do_ functions to add their objects to */ if (jdevs) util_display_json_array(stdout, jdevs, flags); return saved_rc; } int cmd_create_device(int argc, const char **argv, struct daxctl_ctx *ctx) { char *usage = "daxctl create-device []"; int processed, rc; parse_device_options(argc, argv, ACTION_CREATE, create_options, usage, ctx); rc = do_xaction_region(ACTION_CREATE, ctx, &processed); if (rc < 0) fprintf(stderr, "error creating devices: %s\n", strerror(-rc)); fprintf(stderr, "created %d device%s\n", processed, processed == 1 ? "" : "s"); return rc; } int cmd_destroy_device(int argc, const char **argv, struct daxctl_ctx *ctx) { char *usage = "daxctl destroy-device []"; const char *device = parse_device_options(argc, argv, ACTION_DESTROY, destroy_options, usage, ctx); int processed, rc; rc = do_xaction_device(device, ACTION_DESTROY, ctx, &processed); if (rc < 0) fprintf(stderr, "error destroying devices: %s\n", strerror(-rc)); fprintf(stderr, "destroyed %d device%s\n", processed, processed == 1 ? "" : "s"); return rc; } int cmd_reconfig_device(int argc, const char **argv, struct daxctl_ctx *ctx) { char *usage = "daxctl reconfigure-device []"; const char *device = parse_device_options(argc, argv, ACTION_RECONFIG, reconfig_options, usage, ctx); int processed, rc; rc = do_xaction_device(device, ACTION_RECONFIG, ctx, &processed); if (rc < 0) fprintf(stderr, "error reconfiguring devices: %s\n", strerror(-rc)); fprintf(stderr, "reconfigured %d device%s\n", processed, processed == 1 ? "" : "s"); return rc; } int cmd_disable_device(int argc, const char **argv, struct daxctl_ctx *ctx) { char *usage = "daxctl disable-device "; const char *device = parse_device_options(argc, argv, ACTION_DISABLE, disable_options, usage, ctx); int processed, rc; rc = do_xaction_device(device, ACTION_DISABLE, ctx, &processed); if (rc < 0) fprintf(stderr, "error disabling device: %s\n", strerror(-rc)); fprintf(stderr, "disabled %d device%s\n", processed, processed == 1 ? "" : "s"); return rc; } int cmd_enable_device(int argc, const char **argv, struct daxctl_ctx *ctx) { char *usage = "daxctl enable-device "; const char *device = parse_device_options(argc, argv, ACTION_DISABLE, enable_options, usage, ctx); int processed, rc; rc = do_xaction_device(device, ACTION_ENABLE, ctx, &processed); if (rc < 0) fprintf(stderr, "error enabling device: %s\n", strerror(-rc)); fprintf(stderr, "enabled %d device%s\n", processed, processed == 1 ? "" : "s"); return rc; } int cmd_online_memory(int argc, const char **argv, struct daxctl_ctx *ctx) { char *usage = "daxctl online-memory []"; const char *device = parse_device_options(argc, argv, ACTION_ONLINE, online_options, usage, ctx); int processed, rc; rc = do_xaction_device(device, ACTION_ONLINE, ctx, &processed); if (rc < 0) fprintf(stderr, "error onlining memory: %s\n", strerror(-rc)); fprintf(stderr, "onlined memory for %d device%s\n", processed, processed == 1 ? "" : "s"); return rc; } int cmd_offline_memory(int argc, const char **argv, struct daxctl_ctx *ctx) { char *usage = "daxctl offline-memory []"; const char *device = parse_device_options(argc, argv, ACTION_OFFLINE, offline_options, usage, ctx); int processed, rc; rc = do_xaction_device(device, ACTION_OFFLINE, ctx, &processed); if (rc < 0) fprintf(stderr, "error offlining memory: %s\n", strerror(-rc)); fprintf(stderr, "offlined memory for %d device%s\n", processed, processed == 1 ? "" : "s"); return rc; } ndctl-81/daxctl/filter.c000066400000000000000000000020201476737544500153400ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2015-2020 Intel Corporation. All rights reserved. #include #include #include #include "filter.h" struct daxctl_dev *util_daxctl_dev_filter(struct daxctl_dev *dev, const char *ident) { struct daxctl_region *region = daxctl_dev_get_region(dev); int region_id, dev_id; if (!ident || strcmp(ident, "all") == 0) return dev; if (strcmp(ident, daxctl_dev_get_devname(dev)) == 0) return dev; if (sscanf(ident, "%d.%d", ®ion_id, &dev_id) == 2 && daxctl_region_get_id(region) == region_id && daxctl_dev_get_id(dev) == dev_id) return dev; return NULL; } struct daxctl_region *util_daxctl_region_filter(struct daxctl_region *region, const char *ident) { int region_id; if (!ident || strcmp(ident, "all") == 0) return region; if ((sscanf(ident, "%d", ®ion_id) == 1 || sscanf(ident, "region%d", ®ion_id) == 1) && daxctl_region_get_id(region) == region_id) return region; return NULL; } ndctl-81/daxctl/filter.h000066400000000000000000000006751476737544500153630ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (C) 2015-2020 Intel Corporation. All rights reserved. */ #ifndef _DAXCTL_UTIL_FILTER_H_ #define _DAXCTL_UTIL_FILTER_H_ #include #include struct daxctl_dev *util_daxctl_dev_filter(struct daxctl_dev *dev, const char *ident); struct daxctl_region *util_daxctl_region_filter(struct daxctl_region *region, const char *ident); #endif /* _DAXCTL_UTIL_FILTER_H_ */ ndctl-81/daxctl/json.c000066400000000000000000000135501476737544500150360ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2015-2020 Intel Corporation. All rights reserved. #include #include #include #include #include #include #include #include "filter.h" #include "json.h" struct json_object *util_daxctl_dev_to_json(struct daxctl_dev *dev, unsigned long flags) { struct daxctl_memory *mem = daxctl_dev_get_memory(dev); const char *devname = daxctl_dev_get_devname(dev); struct json_object *jdev, *jobj, *jmappings = NULL; struct daxctl_mapping *mapping = NULL; int node, movable, align; jdev = json_object_new_object(); if (!devname || !jdev) return NULL; jobj = json_object_new_string(devname); if (jobj) json_object_object_add(jdev, "chardev", jobj); jobj = util_json_object_size(daxctl_dev_get_size(dev), flags); if (jobj) json_object_object_add(jdev, "size", jobj); node = daxctl_dev_get_target_node(dev); if (node >= 0) { jobj = json_object_new_int(node); if (jobj) json_object_object_add(jdev, "target_node", jobj); } align = daxctl_dev_get_align(dev); if (align > 0) { jobj = util_json_object_size(daxctl_dev_get_align(dev), flags); if (jobj) json_object_object_add(jdev, "align", jobj); } if (mem) jobj = json_object_new_string("system-ram"); else jobj = json_object_new_string("devdax"); if (jobj) json_object_object_add(jdev, "mode", jobj); if (mem && daxctl_dev_get_resource(dev) != 0) { int num_sections = daxctl_memory_num_sections(mem); int num_online = daxctl_memory_is_online(mem); jobj = json_object_new_int(num_online); if (jobj) json_object_object_add(jdev, "online_memblocks", jobj); jobj = json_object_new_int(num_sections); if (jobj) json_object_object_add(jdev, "total_memblocks", jobj); movable = daxctl_memory_is_movable(mem); if (movable == 1) jobj = json_object_new_boolean(true); else if (movable == 0) jobj = json_object_new_boolean(false); else jobj = NULL; if (jobj) json_object_object_add(jdev, "movable", jobj); } if (!daxctl_dev_is_enabled(dev)) { jobj = json_object_new_string("disabled"); if (jobj) json_object_object_add(jdev, "state", jobj); } if (!(flags & UTIL_JSON_DAX_MAPPINGS)) return jdev; daxctl_mapping_foreach(dev, mapping) { struct json_object *jmapping; if (!jmappings) { jmappings = json_object_new_array(); if (!jmappings) continue; json_object_object_add(jdev, "mappings", jmappings); } jmapping = util_daxctl_mapping_to_json(mapping, flags); if (!jmapping) continue; json_object_array_add(jmappings, jmapping); } return jdev; } struct json_object *util_daxctl_devs_to_list(struct daxctl_region *region, struct json_object *jdevs, const char *ident, unsigned long flags) { struct daxctl_dev *dev; daxctl_dev_foreach(region, dev) { struct json_object *jdev; if (!util_daxctl_dev_filter(dev, ident)) continue; if (!(flags & (UTIL_JSON_IDLE|UTIL_JSON_CONFIGURED)) && !daxctl_dev_get_size(dev)) continue; if (!jdevs) { jdevs = json_object_new_array(); if (!jdevs) return NULL; } jdev = util_daxctl_dev_to_json(dev, flags); if (!jdev) { json_object_put(jdevs); return NULL; } json_object_array_add(jdevs, jdev); } return jdevs; } struct json_object *util_daxctl_region_to_json(struct daxctl_region *region, const char *ident, unsigned long flags) { unsigned long align; struct json_object *jregion, *jobj; unsigned long long available_size, size; jregion = json_object_new_object(); if (!jregion) return NULL; /* * The flag indicates when we are being called by an agent that * already knows about the parent device information. */ if (!(flags & UTIL_JSON_DAX)) { /* trim off the redundant /sys/devices prefix */ const char *path = daxctl_region_get_path(region); int len = strlen("/sys/devices"); const char *trim = &path[len]; if (strncmp(path, "/sys/devices", len) != 0) goto err; jobj = json_object_new_string(trim); if (!jobj) goto err; json_object_object_add(jregion, "path", jobj); } jobj = json_object_new_int(daxctl_region_get_id(region)); if (!jobj) goto err; json_object_object_add(jregion, "id", jobj); size = daxctl_region_get_size(region); if (size < ULLONG_MAX) { jobj = util_json_object_size(size, flags); if (!jobj) goto err; json_object_object_add(jregion, "size", jobj); } available_size = daxctl_region_get_available_size(region); if (available_size) { jobj = util_json_object_size(available_size, flags); if (!jobj) goto err; json_object_object_add(jregion, "available_size", jobj); } align = daxctl_region_get_align(region); if (align < ULONG_MAX) { jobj = util_json_new_u64(align); if (!jobj) goto err; json_object_object_add(jregion, "align", jobj); } if (!(flags & UTIL_JSON_DAX_DEVS)) return jregion; jobj = util_daxctl_devs_to_list(region, NULL, ident, flags); if (jobj) json_object_object_add(jregion, "devices", jobj); return jregion; err: json_object_put(jregion); return NULL; } struct json_object *util_daxctl_mapping_to_json(struct daxctl_mapping *mapping, unsigned long flags) { struct json_object *jmapping = json_object_new_object(); struct json_object *jobj; if (!jmapping) return NULL; jobj = util_json_object_hex(daxctl_mapping_get_offset(mapping), flags); if (!jobj) goto err; json_object_object_add(jmapping, "page_offset", jobj); jobj = util_json_object_hex(daxctl_mapping_get_start(mapping), flags); if (!jobj) goto err; json_object_object_add(jmapping, "start", jobj); jobj = util_json_object_hex(daxctl_mapping_get_end(mapping), flags); if (!jobj) goto err; json_object_object_add(jmapping, "end", jobj); jobj = util_json_object_size(daxctl_mapping_get_size(mapping), flags); if (!jobj) goto err; json_object_object_add(jmapping, "size", jobj); return jmapping; err: json_object_put(jmapping); return NULL; } ndctl-81/daxctl/json.h000066400000000000000000000013321476737544500150360ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (C) 2015-2020 Intel Corporation. All rights reserved. */ #ifndef __DAXCTL_JSON_H__ #define __DAXCTL_JSON_H__ #include struct json_object *util_daxctl_mapping_to_json(struct daxctl_mapping *mapping, unsigned long flags); struct daxctl_region; struct daxctl_dev; struct json_object *util_daxctl_region_to_json(struct daxctl_region *region, const char *ident, unsigned long flags); struct json_object *util_daxctl_dev_to_json(struct daxctl_dev *dev, unsigned long flags); struct json_object *util_daxctl_devs_to_list(struct daxctl_region *region, struct json_object *jdevs, const char *ident, unsigned long flags); #endif /* __CXL_UTIL_JSON_H__ */ ndctl-81/daxctl/lib/000077500000000000000000000000001476737544500144635ustar00rootroot00000000000000ndctl-81/daxctl/lib/daxctl.conf000066400000000000000000000000601476737544500166050ustar00rootroot00000000000000blacklist dax_pmem_compat alias nd:t7* dax_pmem ndctl-81/daxctl/lib/libdaxctl-private.h000066400000000000000000000044661476737544500202640ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1 */ /* Copyright (C) 2014-2020, Intel Corporation. All rights reserved. */ #ifndef _LIBDAXCTL_PRIVATE_H_ #define _LIBDAXCTL_PRIVATE_H_ #include #define DAXCTL_EXPORT __attribute__ ((visibility("default"))) enum dax_subsystem { DAX_UNKNOWN, DAX_CLASS, DAX_BUS, }; static const char *dax_subsystems[] = { [DAX_CLASS] = "/sys/class/dax", [DAX_BUS] = "/sys/bus/dax/devices", }; enum daxctl_dev_mode { DAXCTL_DEV_MODE_DEVDAX = 0, DAXCTL_DEV_MODE_RAM, DAXCTL_DEV_MODE_END, }; static const char *dax_modules[] = { [DAXCTL_DEV_MODE_DEVDAX] = "device_dax", [DAXCTL_DEV_MODE_RAM] = "kmem", }; enum memory_op { MEM_SET_OFFLINE, MEM_SET_ONLINE, MEM_SET_ONLINE_NO_MOVABLE, MEM_IS_ONLINE, MEM_COUNT, MEM_GET_ZONE, }; /* OR-able flags, 1, 2, 4, 8 etc */ enum memory_op_status { MEM_ST_OK = 0, MEM_ST_ZONE_INCONSISTENT = 1, }; enum memory_zones { MEM_ZONE_UNKNOWN = 1, MEM_ZONE_MOVABLE, MEM_ZONE_NORMAL, }; static const char *zone_strings[] = { [MEM_ZONE_UNKNOWN] = "mixed", [MEM_ZONE_NORMAL] = "Normal", [MEM_ZONE_MOVABLE] = "Movable", }; static const char *state_strings[] = { [MEM_ZONE_NORMAL] = "online", [MEM_ZONE_MOVABLE] = "online_movable", }; /** * struct daxctl_region - container for dax_devices */ #define REGION_BUF_SIZE 50 struct daxctl_region { int id; uuid_t uuid; int refcount; char *devname; size_t buf_len; void *region_buf; int devices_init; char *region_path; unsigned long align; unsigned long long size; struct daxctl_ctx *ctx; struct list_node list; struct list_head devices; }; struct daxctl_mapping { struct daxctl_dev *dev; unsigned long long pgoff, start, end; struct list_node list; }; struct daxctl_dev { int id, major, minor; void *dev_buf; size_t buf_len; char *dev_path; struct list_node list; unsigned long long resource; unsigned long long size; unsigned long align; struct kmod_module *module; struct daxctl_region *region; struct daxctl_memory *mem; int target_node; int num_mappings; struct list_head mappings; }; struct daxctl_memory { struct daxctl_dev *dev; void *mem_buf; size_t buf_len; char *node_path; unsigned long block_size; enum memory_zones zone; bool auto_online; }; static inline int check_kmod(struct kmod_ctx *kmod_ctx) { return kmod_ctx ? 0 : -ENXIO; } #endif /* _LIBDAXCTL_PRIVATE_H_ */ ndctl-81/daxctl/lib/libdaxctl.c000066400000000000000000001202721476737544500166010ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-2.1 // Copyright (C) 2016-2020, Intel Corporation. All rights reserved. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libdaxctl-private.h" static const char *attrs = "dax_region"; static void free_region(struct daxctl_region *region, struct list_head *head); /** * struct daxctl_ctx - library user context to find "nd" instances * * Instantiate with daxctl_new(), which takes an initial reference. Free * the context by dropping the reference count to zero with * daxctl_unref(), or take additional references with daxctl_ref() * @timeout: default library timeout in milliseconds */ struct daxctl_ctx { /* log_ctx must be first member for daxctl_set_log_fn compat */ struct log_ctx ctx; int refcount; void *userdata; const char *config_path; int regions_init; struct list_head regions; struct kmod_ctx *kmod_ctx; }; /** * daxctl_get_userdata - retrieve stored data pointer from library context * @ctx: daxctl library context * * This might be useful to access from callbacks like a custom logging * function. */ DAXCTL_EXPORT void *daxctl_get_userdata(struct daxctl_ctx *ctx) { if (ctx == NULL) return NULL; return ctx->userdata; } /** * daxctl_set_userdata - store custom @userdata in the library context * @ctx: daxctl library context * @userdata: data pointer */ DAXCTL_EXPORT void daxctl_set_userdata(struct daxctl_ctx *ctx, void *userdata) { if (ctx == NULL) return; ctx->userdata = userdata; } DAXCTL_EXPORT int daxctl_set_config_path(struct daxctl_ctx *ctx, char *config_path) { if ((!ctx) || (!config_path)) return -EINVAL; ctx->config_path = config_path; return 0; } DAXCTL_EXPORT const char *daxctl_get_config_path(struct daxctl_ctx *ctx) { if (ctx == NULL) return NULL; return ctx->config_path; } /** * daxctl_new - instantiate a new library context * @ctx: context to establish * * Returns zero on success and stores an opaque pointer in ctx. The * context is freed by daxctl_unref(), i.e. daxctl_new() implies an * internal daxctl_ref(). */ DAXCTL_EXPORT int daxctl_new(struct daxctl_ctx **ctx) { struct kmod_ctx *kmod_ctx; struct daxctl_ctx *c; int rc = 0; c = calloc(1, sizeof(struct daxctl_ctx)); if (!c) return -ENOMEM; kmod_ctx = kmod_new(NULL, NULL); if (check_kmod(kmod_ctx) != 0) { rc = -ENXIO; goto out; } c->refcount = 1; log_init(&c->ctx, "libdaxctl", "DAXCTL_LOG"); info(c, "ctx %p created\n", c); dbg(c, "log_priority=%d\n", c->ctx.log_priority); *ctx = c; list_head_init(&c->regions); c->kmod_ctx = kmod_ctx; rc = daxctl_set_config_path(c, DAXCTL_CONF_DIR); if (rc) dbg(c, "Unable to set config path: %s\n", strerror(-rc)); return 0; out: free(c); return rc; } /** * daxctl_ref - take an additional reference on the context * @ctx: context established by daxctl_new() */ DAXCTL_EXPORT struct daxctl_ctx *daxctl_ref(struct daxctl_ctx *ctx) { if (ctx == NULL) return NULL; ctx->refcount++; return ctx; } /** * daxctl_unref - drop a context reference count * @ctx: context established by daxctl_new() * * Drop a reference and if the resulting reference count is 0 destroy * the context. */ DAXCTL_EXPORT void daxctl_unref(struct daxctl_ctx *ctx) { struct daxctl_region *region, *_r; if (ctx == NULL) return; ctx->refcount--; if (ctx->refcount > 0) return; list_for_each_safe(&ctx->regions, region, _r, list) free_region(region, &ctx->regions); kmod_unref(ctx->kmod_ctx); info(ctx, "context %p released\n", ctx); free(ctx); } /** * daxctl_set_log_fn - override default log routine * @ctx: daxctl library context * @log_fn: function to be called for logging messages * * The built-in logging writes to stderr. It can be overridden by a * custom function, to plug log messages into the user's logging * functionality. */ DAXCTL_EXPORT void daxctl_set_log_fn(struct daxctl_ctx *ctx, void (*daxctl_log_fn)(struct daxctl_ctx *ctx, int priority, const char *file, int line, const char *fn, const char *format, va_list args)) { ctx->ctx.log_fn = (log_fn) daxctl_log_fn; info(ctx, "custom logging function %p registered\n", daxctl_log_fn); } /** * daxctl_get_log_priority - retrieve current library loglevel (syslog) * @ctx: daxctl library context */ DAXCTL_EXPORT int daxctl_get_log_priority(struct daxctl_ctx *ctx) { return ctx->ctx.log_priority; } /** * daxctl_set_log_priority - set log verbosity * @priority: from syslog.h, LOG_ERR, LOG_INFO, LOG_DEBUG * * Note: LOG_DEBUG requires library be built with "configure --enable-debug" */ DAXCTL_EXPORT void daxctl_set_log_priority(struct daxctl_ctx *ctx, int priority) { ctx->ctx.log_priority = priority; } DAXCTL_EXPORT struct daxctl_ctx *daxctl_region_get_ctx( struct daxctl_region *region) { return region->ctx; } DAXCTL_EXPORT void daxctl_region_get_uuid(struct daxctl_region *region, uuid_t uu) { uuid_copy(uu, region->uuid); } static void free_mem(struct daxctl_dev *dev) { if (dev->mem) { free(dev->mem->node_path); free(dev->mem->mem_buf); free(dev->mem); dev->mem = NULL; } } static void free_dev(struct daxctl_dev *dev, struct list_head *head) { if (head) list_del_from(head, &dev->list); kmod_module_unref(dev->module); free(dev->dev_buf); free(dev->dev_path); free_mem(dev); free(dev); } static void free_region(struct daxctl_region *region, struct list_head *head) { struct daxctl_dev *dev, *_d; list_for_each_safe(®ion->devices, dev, _d, list) free_dev(dev, ®ion->devices); if (head) list_del_from(head, ®ion->list); free(region->region_path); free(region->region_buf); free(region->devname); free(region); } DAXCTL_EXPORT void daxctl_region_unref(struct daxctl_region *region) { struct daxctl_ctx *ctx; if (!region) return; region->refcount--; if (region->refcount) return; ctx = region->ctx; dbg(ctx, "%s: %s\n", __func__, daxctl_region_get_devname(region)); free_region(region, &ctx->regions); } DAXCTL_EXPORT void daxctl_region_ref(struct daxctl_region *region) { if (region) region->refcount++; } static struct daxctl_region *add_dax_region(void *parent, int id, const char *base) { struct daxctl_region *region, *region_dup; struct daxctl_ctx *ctx = parent; char buf[SYSFS_ATTR_SIZE]; char *path; dbg(ctx, "%s: \'%s\'\n", __func__, base); daxctl_region_foreach(ctx, region_dup) if (strcmp(region_dup->region_path, base) == 0) return region_dup; path = calloc(1, strlen(base) + 100); if (!path) return NULL; region = calloc(1, sizeof(*region)); if (!region) goto err_region; region->id = id; region->align = -1; region->size = -1; region->ctx = ctx; region->refcount = 1; list_head_init(®ion->devices); region->devname = strdup(devpath_to_devname(base)); if (!region->devname) goto err_read; sprintf(path, "%s/%s/size", base, attrs); if (sysfs_read_attr(ctx, path, buf) == 0) region->size = strtoull(buf, NULL, 0); sprintf(path, "%s/%s/align", base, attrs); if (sysfs_read_attr(ctx, path, buf) == 0) region->align = strtoul(buf, NULL, 0); region->region_path = strdup(base); if (!region->region_path) goto err_read; region->region_buf = calloc(1, strlen(path) + strlen(attrs) + REGION_BUF_SIZE); if (!region->region_buf) goto err_read; region->buf_len = strlen(path) + REGION_BUF_SIZE; list_add(&ctx->regions, ®ion->list); free(path); return region; err_read: free(region->region_buf); free(region->region_path); free(region->devname); free(region); err_region: free(path); return NULL; } DAXCTL_EXPORT struct daxctl_region *daxctl_new_region(struct daxctl_ctx *ctx, int id, uuid_t uuid, const char *path) { struct daxctl_region *region; region = add_dax_region(ctx, id, path); if (!region) return NULL; uuid_copy(region->uuid, uuid); dbg(ctx, "%s: %s\n", __func__, daxctl_region_get_devname(region)); return region; } static bool device_model_is_dax_bus(struct daxctl_dev *dev) { const char *devname = daxctl_dev_get_devname(dev); struct daxctl_ctx *ctx = daxctl_dev_get_ctx(dev); char *path = dev->dev_buf, *resolved; size_t len = dev->buf_len; struct stat sb; if (snprintf(path, len, "/dev/%s", devname) < 0) return false; if (lstat(path, &sb) < 0) { err(ctx, "%s: stat for %s failed: %s\n", devname, path, strerror(errno)); return false; } if (snprintf(path, len, "/sys/dev/char/%d:%d/subsystem", major(sb.st_rdev), minor(sb.st_rdev)) < 0) return false; resolved = realpath(path, NULL); if (!resolved) { err(ctx, "%s: unable to determine subsys: %s\n", devname, strerror(errno)); return false; } if (strcmp(resolved, "/sys/bus/dax") == 0) { free(resolved); return true; } free(resolved); return false; } DAXCTL_EXPORT int daxctl_dev_is_system_ram_capable(struct daxctl_dev *dev) { const char *devname = daxctl_dev_get_devname(dev); struct daxctl_ctx *ctx = daxctl_dev_get_ctx(dev); char *mod_path, *mod_base; char path[200]; const int len = sizeof(path); if (!device_model_is_dax_bus(dev)) return false; if (!daxctl_dev_is_enabled(dev)) return false; if (snprintf(path, len, "%s/driver", dev->dev_path) >= len) { err(ctx, "%s: buffer too small!\n", devname); return false; } mod_path = realpath(path, NULL); if (!mod_path) return false; mod_base = basename(mod_path); if (strcmp(mod_base, dax_modules[DAXCTL_DEV_MODE_RAM]) == 0) { free(mod_path); return true; } free(mod_path); return false; } /* * This checks for the device to be in system-ram mode, so calling * daxctl_dev_get_memory() on a devdax mode device will always return NULL. */ static struct daxctl_memory *daxctl_dev_alloc_mem(struct daxctl_dev *dev) { const char *size_path = "/sys/devices/system/memory/block_size_bytes"; const char *node_base = "/sys/devices/system/node/node"; const char *devname = daxctl_dev_get_devname(dev); struct daxctl_ctx *ctx = daxctl_dev_get_ctx(dev); struct daxctl_memory *mem; char buf[SYSFS_ATTR_SIZE]; int node_num; if (!daxctl_dev_is_system_ram_capable(dev)) return NULL; mem = calloc(1, sizeof(*mem)); if (!mem) return NULL; mem->dev = dev; if (sysfs_read_attr(ctx, size_path, buf) == 0) { mem->block_size = strtoul(buf, NULL, 16); if (mem->block_size == 0 || mem->block_size == ULONG_MAX) { err(ctx, "%s: Unable to determine memblock size: %s\n", devname, strerror(errno)); mem->block_size = 0; } } node_num = daxctl_dev_get_target_node(dev); if (node_num >= 0) { if (asprintf(&mem->node_path, "%s%d", node_base, node_num) < 0) { err(ctx, "%s: Unable to set node_path\n", devname); goto err_mem; } } mem->mem_buf = calloc(1, strlen(node_base) + 256); if (!mem->mem_buf) goto err_node; mem->buf_len = strlen(node_base) + 256; dev->mem = mem; return mem; err_node: free(mem->node_path); err_mem: free(mem); return NULL; } static void *add_dax_dev(void *parent, int id, const char *daxdev_base) { const char *devname = devpath_to_devname(daxdev_base); char *path = calloc(1, strlen(daxdev_base) + 100); struct daxctl_region *region = parent; struct daxctl_ctx *ctx = region->ctx; struct daxctl_dev *dev, *dev_dup; char buf[SYSFS_ATTR_SIZE]; struct stat st; if (!path) return NULL; dbg(ctx, "%s: base: \'%s\'\n", __func__, daxdev_base); dev = calloc(1, sizeof(*dev)); if (!dev) goto err_dev; dev->id = id; dev->region = region; sprintf(path, "/dev/%s", devname); if (stat(path, &st) < 0) goto err_read; dev->major = major(st.st_rdev); dev->minor = minor(st.st_rdev); sprintf(path, "%s/resource", daxdev_base); if (sysfs_read_attr(ctx, path, buf) == 0) dev->resource = strtoull(buf, NULL, 0); else dev->resource = iomem_get_dev_resource(ctx, daxdev_base); sprintf(path, "%s/size", daxdev_base); if (sysfs_read_attr(ctx, path, buf) < 0) goto err_read; dev->size = strtoull(buf, NULL, 0); /* Device align attribute is only available in v5.10 or up */ sprintf(path, "%s/align", daxdev_base); if (!sysfs_read_attr(ctx, path, buf)) dev->align = strtoull(buf, NULL, 0); else dev->align = 0; dev->dev_path = strdup(daxdev_base); if (!dev->dev_path) goto err_read; dev->dev_buf = calloc(1, strlen(daxdev_base) + 50); if (!dev->dev_buf) goto err_read; dev->buf_len = strlen(daxdev_base) + 50; sprintf(path, "%s/target_node", daxdev_base); if (sysfs_read_attr(ctx, path, buf) == 0) dev->target_node = strtol(buf, NULL, 0); else dev->target_node = -1; daxctl_dev_foreach(region, dev_dup) if (dev_dup->id == dev->id) { free_dev(dev, NULL); free(path); return dev_dup; } dev->num_mappings = -1; list_head_init(&dev->mappings); list_add(®ion->devices, &dev->list); free(path); return dev; err_read: free(dev->dev_buf); free(dev->dev_path); free(dev); err_dev: free(path); return NULL; } DAXCTL_EXPORT int daxctl_region_get_id(struct daxctl_region *region) { return region->id; } DAXCTL_EXPORT unsigned long daxctl_region_get_align(struct daxctl_region *region) { return region->align; } DAXCTL_EXPORT unsigned long long daxctl_region_get_size(struct daxctl_region *region) { return region->size; } DAXCTL_EXPORT const char *daxctl_region_get_devname(struct daxctl_region *region) { return region->devname; } DAXCTL_EXPORT const char *daxctl_region_get_path(struct daxctl_region *region) { return region->region_path; } DAXCTL_EXPORT unsigned long long daxctl_region_get_available_size( struct daxctl_region *region) { struct daxctl_ctx *ctx = daxctl_region_get_ctx(region); char *path = region->region_buf; char buf[SYSFS_ATTR_SIZE], *end; int len = region->buf_len; unsigned long long avail; if (snprintf(path, len, "%s/%s/available_size", region->region_path, attrs) >= len) { err(ctx, "%s: buffer too small!\n", daxctl_region_get_devname(region)); return 0; } if (sysfs_read_attr(ctx, path, buf) < 0) return 0; avail = strtoull(buf, &end, 0); if (buf[0] && *end == '\0') return avail; return 0; } DAXCTL_EXPORT int daxctl_region_create_dev(struct daxctl_region *region) { struct daxctl_ctx *ctx = daxctl_region_get_ctx(region); char *path = region->region_buf; int rc, len = region->buf_len; char *num_devices; if (snprintf(path, len, "%s/%s/create", region->region_path, attrs) >= len) { err(ctx, "%s: buffer too small!\n", daxctl_region_get_devname(region)); return -EFAULT; } if (asprintf(&num_devices, "%d", 1) < 0) { err(ctx, "%s: buffer too small!\n", daxctl_region_get_devname(region)); return -EFAULT; } rc = sysfs_write_attr(ctx, path, num_devices); free(num_devices); return rc; } DAXCTL_EXPORT int daxctl_region_destroy_dev(struct daxctl_region *region, struct daxctl_dev *dev) { struct daxctl_ctx *ctx = daxctl_region_get_ctx(region); char *path = region->region_buf; int rc, len = region->buf_len; if (snprintf(path, len, "%s/%s/delete", region->region_path, attrs) >= len) { err(ctx, "%s: buffer too small!\n", daxctl_region_get_devname(region)); return -EFAULT; } rc = sysfs_write_attr(ctx, path, daxctl_dev_get_devname(dev)); return rc; } DAXCTL_EXPORT struct daxctl_dev *daxctl_region_get_dev_seed( struct daxctl_region *region) { struct daxctl_ctx *ctx = daxctl_region_get_ctx(region); char *path = region->region_buf; int len = region->buf_len; char buf[SYSFS_ATTR_SIZE]; struct daxctl_dev *dev; if (snprintf(path, len, "%s/%s/seed", region->region_path, attrs) >= len) { err(ctx, "%s: buffer too small!\n", daxctl_region_get_devname(region)); return NULL; } if (sysfs_read_attr(ctx, path, buf) < 0) return NULL; daxctl_dev_foreach(region, dev) if (strcmp(buf, daxctl_dev_get_devname(dev)) == 0) return dev; return NULL; } static void dax_devices_init(struct daxctl_region *region) { struct daxctl_ctx *ctx = daxctl_region_get_ctx(region); char daxdev_fmt[50]; size_t i; if (region->devices_init) return; region->devices_init = 1; sprintf(daxdev_fmt, "dax%d.", region->id); for (i = 0; i < ARRAY_SIZE(dax_subsystems); i++) { char *region_path; if (i == DAX_BUS) region_path = region->region_path; else if (i == DAX_CLASS) { if (asprintf(®ion_path, "%s/dax", region->region_path) < 0) { dbg(ctx, "region path alloc fail\n"); continue; } } else continue; sysfs_device_parse(ctx, region_path, daxdev_fmt, region, add_dax_dev); if (i == DAX_CLASS) free(region_path); } } static char *dax_region_path(const char *device, enum dax_subsystem subsys) { char *path, *region_path, *c; if (asprintf(&path, "%s/%s", dax_subsystems[subsys], device) < 0) return NULL; /* dax_region must be the instance's direct parent */ region_path = realpath(path, NULL); free(path); if (!region_path) return NULL; /* * 'region_path' is now regionX/dax/daxX.Y' (DAX_CLASS), or * regionX/daxX.Y (DAX_BUS), trim it back to the regionX * component */ c = strrchr(region_path, '/'); if (!c) { free(region_path); return NULL; } *c = '\0'; if (subsys == DAX_BUS) return region_path; c = strrchr(region_path, '/'); if (!c) { free(region_path); return NULL; } *c = '\0'; return region_path; } static void __dax_regions_init(struct daxctl_ctx *ctx, enum dax_subsystem subsys) { struct dirent *de; DIR *dir = NULL; dir = opendir(dax_subsystems[subsys]); if (!dir) { dbg(ctx, "no dax regions found via: %s\n", dax_subsystems[subsys]); return; } while ((de = readdir(dir)) != NULL) { struct daxctl_region *region; int id, region_id; char *dev_path; if (de->d_ino == 0) continue; if (sscanf(de->d_name, "dax%d.%d", ®ion_id, &id) != 2) continue; dev_path = dax_region_path(de->d_name, subsys); if (!dev_path) { err(ctx, "dax region path allocation failure\n"); continue; } region = add_dax_region(ctx, region_id, dev_path); free(dev_path); if (!region) err(ctx, "add_dax_region() for %s failed\n", de->d_name); } closedir(dir); } static void dax_regions_init(struct daxctl_ctx *ctx) { size_t i; if (ctx->regions_init) return; ctx->regions_init = 1; for (i = 0; i < ARRAY_SIZE(dax_subsystems); i++) { if (i == DAX_UNKNOWN) continue; __dax_regions_init(ctx, i); } } static int is_enabled(const char *drvpath) { struct stat st; if (lstat(drvpath, &st) < 0 || !S_ISLNK(st.st_mode)) return 0; else return 1; } static int daxctl_bind(struct daxctl_ctx *ctx, const char *devname, const char *mod_name) { DIR *dir; int rc = 0; char path[200]; struct dirent *de; const int len = sizeof(path); if (!devname) { err(ctx, "missing devname\n"); return -EINVAL; } if (snprintf(path, len, "/sys/bus/dax/drivers") >= len) { err(ctx, "%s: buffer too small!\n", devname); return -ENXIO; } dir = opendir(path); if (!dir) { err(ctx, "%s: opendir(\"%s\") failed\n", devname, path); return -ENXIO; } while ((de = readdir(dir)) != NULL) { char *drv_path; if (de->d_ino == 0) continue; if (de->d_name[0] == '.') continue; if (strcmp(de->d_name, mod_name) != 0) continue; if (asprintf(&drv_path, "%s/%s/new_id", path, de->d_name) < 0) { err(ctx, "%s: path allocation failure\n", devname); rc = -ENOMEM; break; } rc = sysfs_write_attr_quiet(ctx, drv_path, devname); free(drv_path); if (asprintf(&drv_path, "%s/%s/bind", path, de->d_name) < 0) { err(ctx, "%s: path allocation failure\n", devname); rc = -ENOMEM; break; } rc = sysfs_write_attr_quiet(ctx, drv_path, devname); free(drv_path); break; } closedir(dir); if (rc) { dbg(ctx, "%s: bind failed\n", devname); return rc; } return 0; } static int daxctl_unbind(struct daxctl_ctx *ctx, const char *devpath) { const char *devname = devpath_to_devname(devpath); char path[200]; const int len = sizeof(path); int rc; if (snprintf(path, len, "%s/driver/remove_id", devpath) >= len) { err(ctx, "%s: buffer too small!\n", devname); return -ENXIO; } rc = sysfs_write_attr(ctx, path, devname); if (rc) return rc; if (snprintf(path, len, "%s/driver/unbind", devpath) >= len) { err(ctx, "%s: buffer too small!\n", devname); return -ENXIO; } return sysfs_write_attr(ctx, path, devname); } DAXCTL_EXPORT int daxctl_dev_is_enabled(struct daxctl_dev *dev) { struct daxctl_ctx *ctx = daxctl_dev_get_ctx(dev); char *path = dev->dev_buf; int len = dev->buf_len; if (!device_model_is_dax_bus(dev)) return 1; if (snprintf(path, len, "%s/driver", dev->dev_path) >= len) { err(ctx, "%s: buffer too small!\n", daxctl_dev_get_devname(dev)); return 0; } return is_enabled(path); } static int daxctl_insert_kmod_for_mode(struct daxctl_dev *dev, const char *mod_name) { const char *devname = daxctl_dev_get_devname(dev); struct daxctl_ctx *ctx = daxctl_dev_get_ctx(dev); struct kmod_module *kmod; int rc; rc = kmod_module_new_from_name(ctx->kmod_ctx, mod_name, &kmod); if (rc < 0) { err(ctx, "%s: failed getting module for: %s: %s\n", devname, mod_name, strerror(-rc)); return rc; } /* if the driver is builtin, this Just Works */ dbg(ctx, "%s inserting module: %s\n", devname, kmod_module_get_name(kmod)); rc = kmod_module_probe_insert_module(kmod, KMOD_PROBE_APPLY_BLACKLIST, NULL, NULL, NULL, NULL); if (rc < 0) { err(ctx, "%s: insert failure: %d\n", devname, rc); return rc; } dev->module = kmod; return 0; } static int daxctl_dev_enable(struct daxctl_dev *dev, enum daxctl_dev_mode mode) { struct daxctl_region *region = daxctl_dev_get_region(dev); const char *devname = daxctl_dev_get_devname(dev); struct daxctl_ctx *ctx = daxctl_dev_get_ctx(dev); const char *mod_name = dax_modules[mode]; int rc; if (!device_model_is_dax_bus(dev)) { err(ctx, "%s: error: device model is dax-class\n", devname); err(ctx, "%s: see man daxctl-migrate-device-model\n", devname); return -EOPNOTSUPP; } if (daxctl_dev_is_enabled(dev)) return 0; if (mode >= DAXCTL_DEV_MODE_END || mod_name == NULL) { err(ctx, "%s: Invalid mode: %d\n", devname, mode); return -EINVAL; } rc = daxctl_insert_kmod_for_mode(dev, mod_name); if (rc) return rc; rc = daxctl_bind(ctx, devname, mod_name); if (!daxctl_dev_is_enabled(dev)) { err(ctx, "%s: failed to enable\n", devname); return rc ? rc : -ENXIO; } region->devices_init = 0; dax_devices_init(region); rc = 0; dbg(ctx, "%s: enabled\n", devname); return rc; } DAXCTL_EXPORT int daxctl_dev_enable_devdax(struct daxctl_dev *dev) { return daxctl_dev_enable(dev, DAXCTL_DEV_MODE_DEVDAX); } DAXCTL_EXPORT int daxctl_dev_enable_ram(struct daxctl_dev *dev) { return daxctl_dev_enable(dev, DAXCTL_DEV_MODE_RAM); } DAXCTL_EXPORT int daxctl_dev_disable(struct daxctl_dev *dev) { const char *devname = daxctl_dev_get_devname(dev); struct daxctl_ctx *ctx = daxctl_dev_get_ctx(dev); if (!device_model_is_dax_bus(dev)) { err(ctx, "%s: error: device model is dax-class\n", devname); err(ctx, "%s: see man daxctl-migrate-device-model\n", devname); return -EOPNOTSUPP; } if (!daxctl_dev_is_enabled(dev)) return 0; /* If there is a memory object, first free that */ free_mem(dev); daxctl_unbind(ctx, dev->dev_path); if (daxctl_dev_is_enabled(dev)) { err(ctx, "%s: failed to disable\n", devname); return -EBUSY; } kmod_module_unref(dev->module); dbg(ctx, "%s: disabled\n", devname); return 0; } DAXCTL_EXPORT struct daxctl_ctx *daxctl_dev_get_ctx(struct daxctl_dev *dev) { return dev->region->ctx; } DAXCTL_EXPORT struct daxctl_dev *daxctl_dev_get_first(struct daxctl_region *region) { dax_devices_init(region); return list_top(®ion->devices, struct daxctl_dev, list); } DAXCTL_EXPORT struct daxctl_dev *daxctl_dev_get_next(struct daxctl_dev *dev) { struct daxctl_region *region = dev->region; return list_next(®ion->devices, dev, list); } DAXCTL_EXPORT struct daxctl_region *daxctl_region_get_first( struct daxctl_ctx *ctx) { dax_regions_init(ctx); return list_top(&ctx->regions, struct daxctl_region, list); } DAXCTL_EXPORT struct daxctl_region *daxctl_region_get_next( struct daxctl_region *region) { struct daxctl_ctx *ctx = region->ctx; return list_next(&ctx->regions, region, list); } DAXCTL_EXPORT struct daxctl_region *daxctl_dev_get_region(struct daxctl_dev *dev) { return dev->region; } DAXCTL_EXPORT int daxctl_dev_get_id(struct daxctl_dev *dev) { return dev->id; } DAXCTL_EXPORT const char *daxctl_dev_get_devname(struct daxctl_dev *dev) { return devpath_to_devname(dev->dev_path); } DAXCTL_EXPORT int daxctl_dev_get_major(struct daxctl_dev *dev) { return dev->major; } DAXCTL_EXPORT int daxctl_dev_get_minor(struct daxctl_dev *dev) { return dev->minor; } DAXCTL_EXPORT unsigned long long daxctl_dev_get_resource(struct daxctl_dev *dev) { return dev->resource; } DAXCTL_EXPORT unsigned long long daxctl_dev_get_size(struct daxctl_dev *dev) { return dev->size; } DAXCTL_EXPORT int daxctl_dev_set_size(struct daxctl_dev *dev, unsigned long long size) { struct daxctl_ctx *ctx = daxctl_dev_get_ctx(dev); char buf[SYSFS_ATTR_SIZE]; char *path = dev->dev_buf; int len = dev->buf_len; if (snprintf(path, len, "%s/size", dev->dev_path) >= len) { err(ctx, "%s: buffer too small!\n", daxctl_dev_get_devname(dev)); return -ENXIO; } sprintf(buf, "%#llx\n", size); if (sysfs_write_attr(ctx, path, buf) < 0) { err(ctx, "%s: failed to set size\n", daxctl_dev_get_devname(dev)); return -ENXIO; } dev->size = size; return 0; } DAXCTL_EXPORT unsigned long daxctl_dev_get_align(struct daxctl_dev *dev) { return dev->align; } DAXCTL_EXPORT int daxctl_dev_set_align(struct daxctl_dev *dev, unsigned long align) { struct daxctl_ctx *ctx = daxctl_dev_get_ctx(dev); char buf[SYSFS_ATTR_SIZE]; char *path = dev->dev_buf; int len = dev->buf_len; if (snprintf(path, len, "%s/align", dev->dev_path) >= len) { err(ctx, "%s: buffer too small!\n", daxctl_dev_get_devname(dev)); return -ENXIO; } sprintf(buf, "%#lx\n", align); if (sysfs_write_attr(ctx, path, buf) < 0) { err(ctx, "%s: failed to set align\n", daxctl_dev_get_devname(dev)); return -ENXIO; } dev->align = align; return 0; } DAXCTL_EXPORT int daxctl_dev_set_mapping(struct daxctl_dev *dev, unsigned long long start, unsigned long long end) { struct daxctl_ctx *ctx = daxctl_dev_get_ctx(dev); unsigned long long size = end - start + 1; char buf[SYSFS_ATTR_SIZE]; char *path = dev->dev_buf; int len = dev->buf_len; if (snprintf(path, len, "%s/mapping", dev->dev_path) >= len) { err(ctx, "%s: buffer too small!\n", daxctl_dev_get_devname(dev)); return -ENXIO; } sprintf(buf, "%#llx-%#llx\n", start, end); if (sysfs_write_attr(ctx, path, buf) < 0) { err(ctx, "%s: failed to set mapping\n", daxctl_dev_get_devname(dev)); return -ENXIO; } dev->size += size; return 0; } DAXCTL_EXPORT int daxctl_dev_get_target_node(struct daxctl_dev *dev) { return dev->target_node; } DAXCTL_EXPORT struct daxctl_memory *daxctl_dev_get_memory(struct daxctl_dev *dev) { if (dev->mem) return dev->mem; else return daxctl_dev_alloc_mem(dev); } DAXCTL_EXPORT struct daxctl_dev *daxctl_memory_get_dev(struct daxctl_memory *mem) { return mem->dev; } DAXCTL_EXPORT const char *daxctl_memory_get_node_path(struct daxctl_memory *mem) { return mem->node_path; } DAXCTL_EXPORT unsigned long daxctl_memory_get_block_size(struct daxctl_memory *mem) { return mem->block_size; } static void mappings_init(struct daxctl_dev *dev) { struct daxctl_ctx *ctx = daxctl_dev_get_ctx(dev); char buf[SYSFS_ATTR_SIZE]; char *path = dev->dev_buf; int i; if (dev->num_mappings != -1) return; dev->num_mappings = 0; for (;;) { struct daxctl_mapping *mapping; unsigned long long pgoff, start, end; i = dev->num_mappings; mapping = calloc(1, sizeof(*mapping)); if (!mapping) { err(ctx, "%s: mapping%u allocation failure\n", daxctl_dev_get_devname(dev), i); continue; } sprintf(path, "%s/mapping%d/start", dev->dev_path, i); if (sysfs_read_attr(ctx, path, buf) < 0) { free(mapping); break; } start = strtoull(buf, NULL, 0); sprintf(path, "%s/mapping%d/end", dev->dev_path, i); if (sysfs_read_attr(ctx, path, buf) < 0) { free(mapping); break; } end = strtoull(buf, NULL, 0); sprintf(path, "%s/mapping%d/page_offset", dev->dev_path, i); if (sysfs_read_attr(ctx, path, buf) < 0) { free(mapping); break; } pgoff = strtoull(buf, NULL, 0); mapping->dev = dev; mapping->start = start; mapping->end = end; mapping->pgoff = pgoff; dev->num_mappings++; list_add(&dev->mappings, &mapping->list); } } DAXCTL_EXPORT struct daxctl_mapping *daxctl_mapping_get_first(struct daxctl_dev *dev) { mappings_init(dev); return list_top(&dev->mappings, struct daxctl_mapping, list); } DAXCTL_EXPORT struct daxctl_mapping *daxctl_mapping_get_next(struct daxctl_mapping *mapping) { struct daxctl_dev *dev = mapping->dev; return list_next(&dev->mappings, mapping, list); } DAXCTL_EXPORT unsigned long long daxctl_mapping_get_start(struct daxctl_mapping *mapping) { return mapping->start; } DAXCTL_EXPORT unsigned long long daxctl_mapping_get_end(struct daxctl_mapping *mapping) { return mapping->end; } DAXCTL_EXPORT unsigned long long daxctl_mapping_get_offset(struct daxctl_mapping *mapping) { return mapping->pgoff; } DAXCTL_EXPORT unsigned long long daxctl_mapping_get_size(struct daxctl_mapping *mapping) { return mapping->end - mapping->start + 1; } static int memblock_is_online(struct daxctl_memory *mem, char *memblock) { struct daxctl_dev *dev = daxctl_memory_get_dev(mem); const char *devname = daxctl_dev_get_devname(dev); struct daxctl_ctx *ctx = daxctl_dev_get_ctx(dev); int len = mem->buf_len, rc; char buf[SYSFS_ATTR_SIZE]; char *path = mem->mem_buf; const char *node_path; node_path = daxctl_memory_get_node_path(mem); if (!node_path) return -ENXIO; rc = snprintf(path, len, "%s/%s/state", node_path, memblock); if (rc < 0) return -ENOMEM; rc = sysfs_read_attr(ctx, path, buf); if (rc) { err(ctx, "%s: Failed to read %s: %s\n", devname, path, strerror(-rc)); return rc; } if (strncmp(buf, "online", 6) == 0) return 1; /* offline */ return 0; } static int memblock_is_removable(struct daxctl_memory *mem, char *memblock) { struct daxctl_dev *dev = daxctl_memory_get_dev(mem); const char *devname = daxctl_dev_get_devname(dev); struct daxctl_ctx *ctx = daxctl_dev_get_ctx(dev); int len = mem->buf_len, rc; char buf[SYSFS_ATTR_SIZE]; char *path = mem->mem_buf; const char *node_path; node_path = daxctl_memory_get_node_path(mem); if (!node_path) return -ENXIO; rc = snprintf(path, len, "%s/%s/removable", node_path, memblock); if (rc < 0) return -ENOMEM; rc = sysfs_read_attr(ctx, path, buf); if (rc) { err(ctx, "%s: Failed to read %s: %s\n", devname, path, strerror(-rc)); return rc; } if (strtoul(buf, NULL, 0) == 0) return -EOPNOTSUPP; return 0; } static int online_one_memblock(struct daxctl_memory *mem, char *memblock, enum memory_zones zone, int *status) { struct daxctl_dev *dev = daxctl_memory_get_dev(mem); struct daxctl_ctx *ctx = daxctl_dev_get_ctx(dev); int len = mem->buf_len, rc; char *path = mem->mem_buf; const char *node_path; node_path = daxctl_memory_get_node_path(mem); if (!node_path) return -ENXIO; rc = snprintf(path, len, "%s/%s/state", node_path, memblock); if (rc < 0) return -ENOMEM; rc = memblock_is_online(mem, memblock); if (rc) return rc; switch (zone) { case MEM_ZONE_MOVABLE: case MEM_ZONE_NORMAL: rc = sysfs_write_attr_quiet(ctx, path, state_strings[zone]); break; default: rc = -EINVAL; } if (rc) { /* * If the block got onlined, potentially by some other agent, * do nothing for now. There will be a full scan for zone * correctness later. */ if (memblock_is_online(mem, memblock) == 1) return 0; } return rc; } static int offline_one_memblock(struct daxctl_memory *mem, char *memblock) { struct daxctl_dev *dev = daxctl_memory_get_dev(mem); const char *devname = daxctl_dev_get_devname(dev); struct daxctl_ctx *ctx = daxctl_dev_get_ctx(dev); const char *mode = "offline"; int len = mem->buf_len, rc; char *path = mem->mem_buf; const char *node_path; /* if already offline, there is nothing to do */ rc = memblock_is_online(mem, memblock); if (rc < 0) return rc; if (!rc) return 1; rc = memblock_is_removable(mem, memblock); if (rc) { if (rc == -EOPNOTSUPP) err(ctx, "%s: %s is unremovable\n", devname, memblock); return rc; } node_path = daxctl_memory_get_node_path(mem); if (!node_path) return -ENXIO; rc = snprintf(path, len, "%s/%s/state", node_path, memblock); if (rc < 0) return -ENOMEM; rc = sysfs_write_attr_quiet(ctx, path, mode); if (rc) { /* check if something raced us to offline (unlikely) */ if (!memblock_is_online(mem, memblock)) return 1; err(ctx, "%s: Failed to offline %s: %s\n", devname, path, strerror(-rc)); } return rc; } static int memblock_find_zone(struct daxctl_memory *mem, char *memblock, int *status) { struct daxctl_dev *dev = daxctl_memory_get_dev(mem); const char *devname = daxctl_dev_get_devname(dev); struct daxctl_ctx *ctx = daxctl_dev_get_ctx(dev); enum memory_zones cur_zone; int len = mem->buf_len, rc; char buf[SYSFS_ATTR_SIZE]; char *path = mem->mem_buf; const char *node_path; rc = memblock_is_online(mem, memblock); if (rc < 0) return rc; if (rc == 0) return -ENXIO; node_path = daxctl_memory_get_node_path(mem); if (!node_path) return -ENXIO; rc = snprintf(path, len, "%s/%s/valid_zones", node_path, memblock); if (rc < 0) return -ENOMEM; rc = sysfs_read_attr(ctx, path, buf); if (rc) { err(ctx, "%s: Failed to read %s: %s\n", devname, path, strerror(-rc)); return rc; } if (strcmp(buf, zone_strings[MEM_ZONE_MOVABLE]) == 0) cur_zone = MEM_ZONE_MOVABLE; else if (strcmp(buf, zone_strings[MEM_ZONE_NORMAL]) == 0) cur_zone = MEM_ZONE_NORMAL; else cur_zone = MEM_ZONE_UNKNOWN; if (mem->zone) { if (mem->zone == cur_zone) return 0; else *status |= MEM_ST_ZONE_INCONSISTENT; } else { mem->zone = cur_zone; } return 0; } static int memblock_in_dev(struct daxctl_memory *mem, const char *memblock) { const char *mem_base = "/sys/devices/system/memory/"; struct daxctl_dev *dev = daxctl_memory_get_dev(mem); unsigned long long memblock_res, dev_start, dev_end; const char *devname = daxctl_dev_get_devname(dev); struct daxctl_ctx *ctx = daxctl_dev_get_ctx(dev); int rc, path_len = mem->buf_len; unsigned long memblock_size; char buf[SYSFS_ATTR_SIZE]; unsigned long phys_index; char *path = mem->mem_buf; if (snprintf(path, path_len, "%s/%s/phys_index", mem_base, memblock) < 0) return -ENXIO; rc = sysfs_read_attr(ctx, path, buf); if (rc == 0) { phys_index = strtoul(buf, NULL, 16); if (phys_index == ULONG_MAX) { rc = -errno; err(ctx, "%s: %s: Unable to determine phys_index: %s\n", devname, memblock, strerror(-rc)); return rc; } } else { err(ctx, "%s: %s: Unable to determine phys_index: %s\n", devname, memblock, strerror(-rc)); return rc; } dev_start = daxctl_dev_get_resource(dev); if (!dev_start) { err(ctx, "%s: Unable to determine resource\n", devname); return -EACCES; } dev_end = dev_start + daxctl_dev_get_size(dev) - 1; memblock_size = daxctl_memory_get_block_size(mem); if (!memblock_size) { err(ctx, "%s: Unable to determine memory block size\n", devname); return -ENXIO; } memblock_res = phys_index * memblock_size; if (memblock_res >= dev_start && memblock_res <= dev_end) return 1; return 0; } static int op_for_one_memblock(struct daxctl_memory *mem, char *memblock, enum memory_op op, int *status) { struct daxctl_dev *dev = daxctl_memory_get_dev(mem); const char *devname = daxctl_dev_get_devname(dev); struct daxctl_ctx *ctx = daxctl_dev_get_ctx(dev); int rc; switch (op) { case MEM_SET_ONLINE: return online_one_memblock(mem, memblock, MEM_ZONE_MOVABLE, status); case MEM_SET_ONLINE_NO_MOVABLE: return online_one_memblock(mem, memblock, MEM_ZONE_NORMAL, status); case MEM_SET_OFFLINE: return offline_one_memblock(mem, memblock); case MEM_IS_ONLINE: rc = memblock_is_online(mem, memblock); if (rc < 0) return rc; /* * Retain the 'normal' semantics for if (memblock_is_online()), * but since count needs rc == 0, we'll just flip rc for this op */ return !rc; case MEM_COUNT: return 0; case MEM_GET_ZONE: return memblock_find_zone(mem, memblock, status); } err(ctx, "%s: BUG: unknown op: %d\n", devname, op); return -EINVAL; } static int daxctl_memory_op(struct daxctl_memory *mem, enum memory_op op) { struct daxctl_dev *dev = daxctl_memory_get_dev(mem); const char *devname = daxctl_dev_get_devname(dev); struct daxctl_ctx *ctx = daxctl_dev_get_ctx(dev); int rc, count = 0, status_flags = 0; const char *node_path; struct dirent *de; DIR *node_dir; node_path = daxctl_memory_get_node_path(mem); if (!node_path) { err(ctx, "%s: Failed to get node_path\n", devname); return -ENXIO; } node_dir = opendir(node_path); if (!node_dir) return -errno; errno = 0; while ((de = readdir(node_dir)) != NULL) { if (strncmp(de->d_name, "memory", 6) == 0) { if (strncmp(de->d_name, "memory_", 7) == 0) continue; rc = memblock_in_dev(mem, de->d_name); if (rc < 0) goto out_dir; if (rc == 0) /* memblock not in dev */ continue; /* memblock is in dev, perform op */ rc = op_for_one_memblock(mem, de->d_name, op, &status_flags); if (rc < 0) goto out_dir; if (rc == 0) count++; } errno = 0; } if (status_flags & MEM_ST_ZONE_INCONSISTENT) mem->zone = MEM_ZONE_UNKNOWN; if (errno) { rc = -errno; goto out_dir; } rc = count; out_dir: closedir(node_dir); return rc; } /* * daxctl_memory_online() will online to ZONE_MOVABLE by default */ static int daxctl_memory_online_with_zone(struct daxctl_memory *mem, enum memory_zones zone) { struct daxctl_dev *dev = daxctl_memory_get_dev(mem); const char *devname = daxctl_dev_get_devname(dev); struct daxctl_ctx *ctx = daxctl_dev_get_ctx(dev); int rc; switch (zone) { case MEM_ZONE_MOVABLE: rc = daxctl_memory_op(mem, MEM_SET_ONLINE); break; case MEM_ZONE_NORMAL: rc = daxctl_memory_op(mem, MEM_SET_ONLINE_NO_MOVABLE); break; default: err(ctx, "%s: BUG: invalid zone for onlining\n", devname); rc = -EINVAL; } if (rc) return rc; /* * Detect any potential races when blocks were being brought online by * checking the zone in which the memory blocks are at this point. If * any of the blocks are not in ZONE_MOVABLE, emit a warning. */ mem->zone = 0; rc = daxctl_memory_op(mem, MEM_GET_ZONE); if (rc < 0) return rc; if (mem->zone != zone) { err(ctx, "%s:\n WARNING: detected a race while onlining memory\n" " See 'man daxctl-reconfigure-device' for more details\n", devname); return -EBUSY; } return rc; } DAXCTL_EXPORT int daxctl_memory_online(struct daxctl_memory *mem) { return daxctl_memory_online_with_zone(mem, MEM_ZONE_MOVABLE); } DAXCTL_EXPORT int daxctl_memory_online_no_movable(struct daxctl_memory *mem) { return daxctl_memory_online_with_zone(mem, MEM_ZONE_NORMAL); } DAXCTL_EXPORT int daxctl_memory_offline(struct daxctl_memory *mem) { return daxctl_memory_op(mem, MEM_SET_OFFLINE); } DAXCTL_EXPORT int daxctl_memory_is_online(struct daxctl_memory *mem) { return daxctl_memory_op(mem, MEM_IS_ONLINE); } DAXCTL_EXPORT int daxctl_memory_num_sections(struct daxctl_memory *mem) { return daxctl_memory_op(mem, MEM_COUNT); } DAXCTL_EXPORT int daxctl_memory_is_movable(struct daxctl_memory *mem) { int rc; /* Start a fresh zone scan, clear any previous info */ mem->zone = 0; rc = daxctl_memory_op(mem, MEM_GET_ZONE); if (rc < 0) return rc; return (mem->zone == MEM_ZONE_MOVABLE) ? 1 : 0; } DAXCTL_EXPORT int daxctl_dev_will_auto_online_memory(struct daxctl_dev *dev) { const char *auto_path = "/sys/devices/system/memory/auto_online_blocks"; const char *devname = daxctl_dev_get_devname(dev); struct daxctl_ctx *ctx = daxctl_dev_get_ctx(dev); char buf[SYSFS_ATTR_SIZE]; /* * If we can't read the policy for some reason, don't fail yet. Assume * the auto-onlining policy is absent, and carry on. If onlining blocks * does result in the memory being in an inconsistent state, we have a * check and warning for it after the fact */ if (sysfs_read_attr(ctx, auto_path, buf) != 0) err(ctx, "%s: Unable to determine auto-online policy: %s\n", devname, strerror(errno)); /* match both "online" and "online_movable" */ return !strncmp(buf, "online", 6); } DAXCTL_EXPORT int daxctl_dev_has_online_memory(struct daxctl_dev *dev) { struct daxctl_memory *mem = daxctl_dev_get_memory(dev); if (mem) return daxctl_memory_is_online(mem); else return 0; } ndctl-81/daxctl/lib/libdaxctl.pc.in000066400000000000000000000003401476737544500173570ustar00rootroot00000000000000prefix=@prefix@ exec_prefix=@exec_prefix@ libdir=@libdir@ includedir=@includedir@ Name: libdaxctl Description: Manage "Device DAX" devices Version: @VERSION@ Libs: -L${libdir} -ldaxctl Libs.private: Cflags: -I${includedir} ndctl-81/daxctl/lib/libdaxctl.sym000066400000000000000000000037241476737544500171710ustar00rootroot00000000000000LIBDAXCTL_1 { global: daxctl_get_userdata; daxctl_set_userdata; daxctl_ref; daxctl_get_log_priority; daxctl_set_log_fn; daxctl_unref; daxctl_set_log_priority; daxctl_new; local: *; }; LIBDAXCTL_2 { global: daxctl_region_unref; daxctl_new_region; daxctl_region_ref; daxctl_region_unref; daxctl_region_get_uuid; daxctl_region_get_id; daxctl_region_get_ctx; daxctl_dev_get_first; daxctl_dev_get_next; daxctl_dev_get_region; daxctl_dev_get_id; daxctl_dev_get_devname; daxctl_dev_get_major; daxctl_dev_get_minor; daxctl_dev_get_size; } LIBDAXCTL_1; LIBDAXCTL_3 { global: daxctl_region_get_available_size; daxctl_region_get_devname; daxctl_region_get_dev_seed; } LIBDAXCTL_2; LIBDAXCTL_4 { global: daxctl_region_get_size; daxctl_region_get_align; daxctl_region_get_first; daxctl_region_get_next; } LIBDAXCTL_3; LIBDAXCTL_5 { global: daxctl_region_get_path; } LIBDAXCTL_4; LIBDAXCTL_6 { global: daxctl_dev_get_ctx; daxctl_dev_is_enabled; daxctl_dev_disable; daxctl_dev_enable_devdax; daxctl_dev_enable_ram; daxctl_dev_get_resource; daxctl_dev_get_target_node; daxctl_dev_get_memory; daxctl_memory_get_dev; daxctl_memory_get_node_path; daxctl_memory_get_block_size; daxctl_memory_online; daxctl_memory_offline; daxctl_memory_is_online; daxctl_memory_num_sections; } LIBDAXCTL_5; LIBDAXCTL_7 { global: daxctl_memory_is_movable; daxctl_memory_online_no_movable; } LIBDAXCTL_6; LIBDAXCTL_8 { global: daxctl_dev_set_size; daxctl_region_create_dev; daxctl_region_destroy_dev; daxctl_dev_get_align; daxctl_dev_set_align; daxctl_mapping_get_first; daxctl_mapping_get_next; daxctl_mapping_get_start; daxctl_mapping_get_end; daxctl_mapping_get_offset; daxctl_mapping_get_size; daxctl_dev_set_mapping; } LIBDAXCTL_7; LIBDAXCTL_9 { global: daxctl_dev_will_auto_online_memory; daxctl_dev_has_online_memory; daxctl_set_config_path; daxctl_get_config_path; } LIBDAXCTL_8; LIBDAXCTL_10 { global: daxctl_dev_is_system_ram_capable; } LIBDAXCTL_9; ndctl-81/daxctl/lib/meson.build000066400000000000000000000017421476737544500166310ustar00rootroot00000000000000libdaxctl_version = '@0@.@1@.@2@'.format( LIBDAXCTL_CURRENT - LIBDAXCTL_AGE, LIBDAXCTL_REVISION, LIBDAXCTL_AGE, ) libdaxctl_dir_path = meson.current_source_dir() libdaxctl_sym = files('libdaxctl.sym') libdaxctl_sym_path = libdaxctl_dir_path / 'libdaxctl.sym' libdaxctl_src = [ '../../util/iomem.c', '../../util/sysfs.c', '../../util/log.c', 'libdaxctl.c', ] daxctl = library( 'daxctl', libdaxctl_src, version : libdaxctl_version, include_directories : root_inc, dependencies : [ uuid, kmod, ], install : true, install_dir : rootlibdir, link_args : '-Wl,--version-script=' + libdaxctl_sym_path, link_depends : libdaxctl_sym, ) daxctl_dep = declare_dependency(link_with : daxctl) custom_target( 'libdaxctl.pc', command : pkgconfig_script + [ '@INPUT@' ], input : 'libdaxctl.pc.in', output : 'libdaxctl.pc', capture : true, install : true, install_dir : pkgconfiglibdir, ) install_data('daxctl.conf', install_dir : datadir / 'daxctl') ndctl-81/daxctl/libdaxctl.h000066400000000000000000000134371476737544500160440ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1 */ /* Copyright (C) 2016-2020, Intel Corporation. All rights reserved. */ #ifndef _LIBDAXCTL_H_ #define _LIBDAXCTL_H_ #include #include #ifdef HAVE_UUID #include #else typedef unsigned char uuid_t[16]; #endif #ifdef __cplusplus extern "C" { #endif struct daxctl_ctx; struct daxctl_ctx *daxctl_ref(struct daxctl_ctx *ctx); void daxctl_unref(struct daxctl_ctx *ctx); int daxctl_new(struct daxctl_ctx **ctx); void daxctl_set_log_fn(struct daxctl_ctx *ctx, void (*log_fn)(struct daxctl_ctx *ctx, int priority, const char *file, int line, const char *fn, const char *format, va_list args)); int daxctl_get_log_priority(struct daxctl_ctx *ctx); void daxctl_set_log_priority(struct daxctl_ctx *ctx, int priority); void daxctl_set_userdata(struct daxctl_ctx *ctx, void *userdata); void *daxctl_get_userdata(struct daxctl_ctx *ctx); int daxctl_set_config_path(struct daxctl_ctx *ctx, char *config_path); const char *daxctl_get_config_path(struct daxctl_ctx *ctx); struct daxctl_region; struct daxctl_region *daxctl_new_region(struct daxctl_ctx *ctx, int id, uuid_t uuid, const char *path); struct daxctl_region *daxctl_region_get_first(struct daxctl_ctx *ctx); struct daxctl_region *daxctl_region_get_next(struct daxctl_region *region); void daxctl_region_ref(struct daxctl_region *region); void daxctl_region_unref(struct daxctl_region *region); void daxctl_region_get_uuid(struct daxctl_region *region, uuid_t uu); int daxctl_region_get_id(struct daxctl_region *region); struct daxctl_ctx *daxctl_region_get_ctx(struct daxctl_region *region); unsigned long long daxctl_region_get_available_size( struct daxctl_region *region); unsigned long long daxctl_region_get_size(struct daxctl_region *region); unsigned long daxctl_region_get_align(struct daxctl_region *region); const char *daxctl_region_get_devname(struct daxctl_region *region); const char *daxctl_region_get_path(struct daxctl_region *region); int daxctl_region_create_dev(struct daxctl_region *region); struct daxctl_dev *daxctl_region_get_dev_seed(struct daxctl_region *region); struct daxctl_dev; struct daxctl_dev *daxctl_dev_get_first(struct daxctl_region *region); int daxctl_region_destroy_dev(struct daxctl_region *region, struct daxctl_dev *dev); struct daxctl_dev *daxctl_dev_get_next(struct daxctl_dev *dev); struct daxctl_region *daxctl_dev_get_region(struct daxctl_dev *dev); int daxctl_dev_get_id(struct daxctl_dev *dev); const char *daxctl_dev_get_devname(struct daxctl_dev *dev); int daxctl_dev_get_major(struct daxctl_dev *dev); int daxctl_dev_get_minor(struct daxctl_dev *dev); unsigned long long daxctl_dev_get_resource(struct daxctl_dev *dev); unsigned long long daxctl_dev_get_size(struct daxctl_dev *dev); int daxctl_dev_set_size(struct daxctl_dev *dev, unsigned long long size); unsigned long daxctl_dev_get_align(struct daxctl_dev *dev); int daxctl_dev_set_align(struct daxctl_dev *dev, unsigned long align); int daxctl_dev_set_mapping(struct daxctl_dev *dev, unsigned long long start, unsigned long long end); struct daxctl_ctx *daxctl_dev_get_ctx(struct daxctl_dev *dev); int daxctl_dev_is_enabled(struct daxctl_dev *dev); int daxctl_dev_disable(struct daxctl_dev *dev); int daxctl_dev_enable_devdax(struct daxctl_dev *dev); int daxctl_dev_enable_ram(struct daxctl_dev *dev); int daxctl_dev_get_target_node(struct daxctl_dev *dev); int daxctl_dev_will_auto_online_memory(struct daxctl_dev *dev); int daxctl_dev_has_online_memory(struct daxctl_dev *dev); struct daxctl_memory; int daxctl_dev_is_system_ram_capable(struct daxctl_dev *dev); struct daxctl_memory *daxctl_dev_get_memory(struct daxctl_dev *dev); struct daxctl_dev *daxctl_memory_get_dev(struct daxctl_memory *mem); const char *daxctl_memory_get_node_path(struct daxctl_memory *mem); unsigned long daxctl_memory_get_block_size(struct daxctl_memory *mem); int daxctl_memory_online(struct daxctl_memory *mem); int daxctl_memory_offline(struct daxctl_memory *mem); int daxctl_memory_is_online(struct daxctl_memory *mem); int daxctl_memory_num_sections(struct daxctl_memory *mem); int daxctl_memory_is_movable(struct daxctl_memory *mem); int daxctl_memory_online_no_movable(struct daxctl_memory *mem); #define daxctl_dev_foreach(region, dev) \ for (dev = daxctl_dev_get_first(region); \ dev != NULL; \ dev = daxctl_dev_get_next(dev)) #define daxctl_dev_foreach_safe(region, dev, _dev) \ for (dev = daxctl_dev_get_first(region), \ _dev = dev ? daxctl_dev_get_next(dev) : NULL; \ dev != NULL; \ dev = _dev, \ _dev = _dev ? daxctl_dev_get_next(_dev) : NULL) #define daxctl_region_foreach(ctx, region) \ for (region = daxctl_region_get_first(ctx); \ region != NULL; \ region = daxctl_region_get_next(region)) #define daxctl_region_foreach_safe(ctx, region, _region) \ for (region = daxctl_region_get_first(ctx), \ _region = region ? daxctl_region_get_next(region) : NULL; \ region != NULL; \ region = _region, \ _region = _region ? daxctl_region_get_next(_region) : NULL) struct daxctl_mapping; struct daxctl_mapping *daxctl_mapping_get_first(struct daxctl_dev *dev); struct daxctl_mapping *daxctl_mapping_get_next(struct daxctl_mapping *mapping); #define daxctl_mapping_foreach(dev, mapping) \ for (mapping = daxctl_mapping_get_first(dev); \ mapping != NULL; \ mapping = daxctl_mapping_get_next(mapping)) unsigned long long daxctl_mapping_get_start(struct daxctl_mapping *mapping); unsigned long long daxctl_mapping_get_end(struct daxctl_mapping *mapping); unsigned long long daxctl_mapping_get_offset(struct daxctl_mapping *mapping); unsigned long long daxctl_mapping_get_size(struct daxctl_mapping *mapping); #ifdef __cplusplus } /* extern "C" */ #endif #endif ndctl-81/daxctl/list.c000066400000000000000000000060021476737544500150320ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2015-2020 Intel Corporation. All rights reserved. #include #include #include #include #include #include #include #include #include #include #include "filter.h" #include "json.h" static struct { bool devs; bool regions; bool mappings; bool idle; bool human; } list; static unsigned long listopts_to_flags(void) { unsigned long flags = 0; if (list.devs) flags |= UTIL_JSON_DAX_DEVS; if (list.mappings) flags |= UTIL_JSON_DAX_MAPPINGS; if (list.idle) flags |= UTIL_JSON_IDLE; if (list.human) flags |= UTIL_JSON_HUMAN; return flags; } static struct { const char *dev; const char *region; } param; static int did_fail; #define fail(fmt, ...) \ do { \ did_fail = 1; \ fprintf(stderr, "daxctl-%s:%s:%d: " fmt, \ VERSION, __func__, __LINE__, ##__VA_ARGS__); \ } while (0) static int num_list_flags(void) { return list.regions + list.devs; } int cmd_list(int argc, const char **argv, struct daxctl_ctx *ctx) { const struct option options[] = { OPT_STRING('r', "region", ¶m.region, "region-id", "filter by region"), OPT_STRING('d', "dev", ¶m.dev, "dev-id", "filter by dax device instance name"), OPT_BOOLEAN('D', "devices", &list.devs, "include dax device info"), OPT_BOOLEAN('R', "regions", &list.regions, "include dax region info"), OPT_BOOLEAN('M', "mappings", &list.mappings, "include dax mappings info"), OPT_BOOLEAN('i', "idle", &list.idle, "include idle devices"), OPT_BOOLEAN('u', "human", &list.human, "use human friendly number formats "), OPT_END(), }; const char * const u[] = { "daxctl list []", NULL }; struct json_object *jregions = NULL; struct json_object *jdevs = NULL; struct daxctl_region *region; unsigned long list_flags; int i; argc = parse_options(argc, argv, options, u, 0); for (i = 0; i < argc; i++) error("unknown parameter \"%s\"\n", argv[i]); if (argc) usage_with_options(u, options); if (num_list_flags() == 0) { list.regions = !!param.region; list.devs = !!param.dev; } if (num_list_flags() == 0) list.devs = true; list_flags = listopts_to_flags(); daxctl_region_foreach(ctx, region) { struct json_object *jregion = NULL; if (!util_daxctl_region_filter(region, param.region)) continue; if (list.regions) { if (!jregions) { jregions = json_object_new_array(); if (!jregions) { fail("\n"); continue; } } jregion = util_daxctl_region_to_json(region, param.dev, list_flags); if (!jregion) { fail("\n"); continue; } json_object_array_add(jregions, jregion); } else if (list.devs) jdevs = util_daxctl_devs_to_list(region, jdevs, param.dev, list_flags); } if (jregions) util_display_json_array(stdout, jregions, list_flags); else if (jdevs) util_display_json_array(stdout, jdevs, list_flags); if (did_fail) return -ENOMEM; return 0; } ndctl-81/daxctl/meson.build000066400000000000000000000012261476737544500160600ustar00rootroot00000000000000daxctl_src = [ 'daxctl.c', 'acpi.c', 'list.c', 'migrate.c', 'device.c', 'json.c', 'filter.c', ] daxctl_tool = executable('daxctl', daxctl_src, include_directories : root_inc, dependencies : [ daxctl_dep, ndctl_dep, util_dep, uuid, kmod, json, versiondep, ], install : true, install_dir : rootbindir, ) install_headers('libdaxctl.h', subdir : 'daxctl') install_data('daxctl.example.conf', install_dir : daxctlconf_dir ) if get_option('systemd').enabled() install_data('90-daxctl-device.rules', install_dir : udevrulesdir) install_data('daxdev-reconfigure@.service', install_dir : systemdunitdir) endif ndctl-81/daxctl/migrate.c000066400000000000000000000017161476737544500155160ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) 2019-2020 Intel Corporation. All rights reserved. */ #include #include #include #include #include #include #include #include int cmd_migrate(int argc, const char **argv, struct daxctl_ctx *ctx) { int i; static const struct option options[] = { OPT_END(), }; const char * const u[] = { "daxctl migrate-device-model", NULL }; argc = parse_options(argc, argv, options, u, 0); for (i = 0; i < argc; i++) error("unknown parameter \"%s\"\n", argv[i]); if (argc) usage_with_options(u, options); if (symlink(DAXCTL_MODPROBE_DATA, DAXCTL_MODPROBE_INSTALL) == 0) { fprintf(stderr, " success: installed %s\n", DAXCTL_MODPROBE_INSTALL); return EXIT_SUCCESS; } error("failed to install %s: %s\n", DAXCTL_MODPROBE_INSTALL, strerror(errno)); return EXIT_FAILURE; } ndctl-81/git-version000077500000000000000000000016471476737544500146420ustar00rootroot00000000000000#!/bin/bash to_ver() { VN=$1 #drop leading 'v' out of the version so its a pure number if [ ${VN:0:1} = "v" ]; then VN=${VN:1} fi echo $VN } dirty() { VN=$(to_ver $1) git update-index -q --refresh if test -z "$(git diff-index --name-only HEAD --)"; then echo "$VN" else echo "${VN}.dirty" fi } DEF_VER=81 LF=' ' # First see if there is a version file (included in release tarballs), # then try git-describe, then default. if test -f version; then VN=$(cat version) || VN="$DEF_VER" elif test -d ${GIT_DIR:-.git} -o -f .git && VN=$(git describe --match "v[0-9]*" --abbrev=7 HEAD 2>/dev/null) && case "$VN" in *$LF*) (exit 1) ;; v[0-9]*) VN="$(dirty $VN)" esac; then VN=$(echo "$VN" | sed -e 's/-/./g'); else read COMMIT COMMIT_SUBJECT </dev/null) EOF if [ -z $COMMIT ]; then VN="${DEF_VER}+" else VN="$(dirty ${DEF_VER}.git$COMMIT)" fi fi echo $VN ndctl-81/git-version-gen000077500000000000000000000004421476737544500154010ustar00rootroot00000000000000#!/bin/sh GVF=version.m4 if test -r $GVF; then VC=$(sed -e 's/m4_define(\[GIT_VERSION], \[//' <$GVF) VC=$(echo $VC | sed -e 's/\])//') else VC=unset fi VN=$(./git-version) test "$VN" = "$VC" || { echo >&2 "GIT_VERSION = $VN" echo "m4_define([GIT_VERSION], [$VN])" >$GVF exit 0 } ndctl-81/make-git-snapshot.sh000077500000000000000000000013151476737544500163300ustar00rootroot00000000000000#!/bin/bash set -e NAME=ndctl if [ ! -x ./git-version ]; then echo "$0 : ERROR: Must run from top level of git tree" exit 1 fi REFDIR=$PWD UPSTREAM=$REFDIR #TODO update once we have a public upstream OUTDIR=$HOME/rpmbuild/SOURCES if [ ! -d $OUTDIR ]; then mkdir -p $OUTDIR fi [ -n "$1" ] && HEAD="$1" || HEAD="HEAD" WORKDIR="$(mktemp -d --tmpdir "$NAME.XXXXXXXXXX")" trap 'rm -rf $WORKDIR' exit [ -d "$REFDIR" ] && REFERENCE="--reference $REFDIR" git clone $REFERENCE "$UPSTREAM" "$WORKDIR" VERSION=$(./git-version) DIRNAME="ndctl-${VERSION}" git archive --remote="$WORKDIR" --format=tar --prefix="$DIRNAME/" HEAD | gzip > $OUTDIR/"ndctl-${VERSION}.tar.gz" echo "Written $OUTDIR/ndctl-${VERSION}.tar.gz" ndctl-81/meson.build000066400000000000000000000226271476737544500146110ustar00rootroot00000000000000project('ndctl', 'c', version : '81', license : [ 'GPL-2.0', 'LGPL-2.1', 'CC0-1.0', 'MIT', ], default_options : [ 'c_std=gnu99', 'prefix=/usr', 'libdir=/usr/lib', 'sysconfdir=/etc', 'localstatedir=/var', ], ) # rootprefixdir and rootlibdir setup copied from systemd: rootprefixdir = get_option('rootprefix') rootprefix_default = '/usr' if rootprefixdir == '' rootprefixdir = rootprefix_default endif rootbindir = join_paths(rootprefixdir, 'bin') # join_paths ignores the preceding arguments if an absolute component is # encountered, so this should canonicalize various paths when they are # absolute or relative. prefixdir = get_option('prefix') if not prefixdir.startswith('/') error('Prefix is not absolute: "@0@"'.format(prefixdir)) endif if prefixdir != rootprefixdir and rootprefixdir != '/' and not prefixdir.strip('/').startswith(rootprefixdir.strip('/') + '/') error('Prefix is not below root prefix (now rootprefix=@0@ prefix=@1@)'.format( rootprefixdir, prefixdir)) endif libdir = join_paths(prefixdir, get_option('libdir')) rootlibdir = get_option('rootlibdir') if rootlibdir == '' rootlibdir = join_paths(rootprefixdir, libdir.split('/')[-1]) endif datadir = prefixdir / get_option('datadir') includedir = prefixdir / get_option('includedir') pkgconfiglibdir = get_option('pkgconfiglibdir') != '' ? get_option('pkgconfiglibdir') : libdir / 'pkgconfig' datadir = prefixdir / get_option('datadir') includedir = prefixdir / get_option('includedir') sysconfdir = get_option('sysconfdir') pkgconfig_script = ''' sed -e s,@VERSION@,@0@,g -e s,@prefix@,@1@,g -e s,@exec_prefix@,@1@,g -e s,@libdir@,@2@,g -e s,@includedir@,@3@,g '''.format(meson.project_version(), prefixdir, libdir, includedir).split() cc_flags = [ '-Wchar-subscripts', '-Wformat-security', '-Wmissing-declarations', '-Wmissing-prototypes', '-Wnested-externs ', '-Wshadow', '-Wsign-compare', '-Wstrict-prototypes', '-Wtype-limits', '-Wmaybe-uninitialized', '-Wdeclaration-after-statement', '-Wunused-result', ] if get_option('optimization') != '0' cc_flags += [ '-D_FORTIFY_SOURCE=2' ] endif cc = meson.get_compiler('c') add_project_arguments(cc.get_supported_arguments(cc_flags), language : 'c') project_source_root = meson.current_source_dir() # Remove this after the conversion to meson has been completed # Cleanup the leftover config.h files to avoid conflicts with the meson # generated config.h git = find_program('git', required : false) env = find_program('env') if git.found() run_command('clean_config.sh', env : 'GIT_DIR=@0@/.git'.format(project_source_root), check : false, ) endif version_tag = get_option('version-tag') if version_tag != '' vcs_data = configuration_data() vcs_data.set('VCS_TAG', version_tag) version_h = configure_file( configuration : vcs_data, input : 'version.h.in', output : 'version.h' ) else vcs_tagger = [ project_source_root + '/tools/meson-vcs-tag.sh', project_source_root, meson.project_version() ] version_h = vcs_tag( input : 'version.h.in', output : 'version.h', command: vcs_tagger ) endif if git.found() all_files = run_command( env, '-u', 'GIT_WORK_TREE', git, '--git-dir=@0@/.git'.format(project_source_root), 'ls-files', ':/*.[ch]', check : false) if all_files.returncode() == 0 all_files = files(all_files.stdout().split()) custom_target( 'tags', output : 'tags', command : [env, 'etags', '-o', '@0@/TAGS'.format(project_source_root)] + all_files) run_target( 'ctags', command : [env, 'ctags', '-o', '@0@/tags'.format(project_source_root)] + all_files) endif endif versiondep = declare_dependency( compile_args: ['-include', 'version.h'], sources: version_h ) kmod = dependency('libkmod') libudev = dependency('libudev') uuid = dependency('uuid') json = dependency('json-c') if get_option('libtracefs').enabled() traceevent = dependency('libtraceevent') tracefs = dependency('libtracefs', version : '>=1.2.0') endif if get_option('docs').enabled() if get_option('asciidoctor').enabled() asciidoc = find_program('asciidoctor', required : true) else asciidoc = find_program('asciidoc', required : true) xmlto = find_program('xmlto', required : true) endif endif if get_option('systemd').enabled() systemd = dependency('systemd', required : true) systemdunitdir = systemd.get_pkgconfig_variable('systemdsystemunitdir') udev = dependency('udev', required : true) udevdir = udev.get_pkgconfig_variable('udevdir') udevrulesdir = udevdir / 'rules.d' endif cc = meson.get_compiler('c') # keyutils lacks pkgconfig keyutils = cc.find_library('keyutils', required : get_option('keyutils')) # iniparser lacks pkgconfig and its header files are either at '/usr/include' or '/usr/include/iniparser' # Use the path provided by user via meson configure -Diniparserdir= # if thats not provided then try searching for 'iniparser.h' in default system include path # and if that not found then as a last resort try looking at '/usr/include/iniparser' iniparser_headers = ['iniparser.h', 'dictionary.h'] message('Looking for iniparser include headers', iniparser_headers) iniparserdir = include_directories(includedir / get_option('iniparserdir'), is_system:true) iniparser = cc.find_library('iniparser', required : (get_option('iniparserdir') != '') , has_headers :iniparser_headers ,header_include_directories : iniparserdir) if not iniparser.found() iniparserdir = include_directories(includedir / 'iniparser', is_system:true) iniparser = cc.find_library('iniparser', required : true, has_headers : iniparser_headers, header_include_directories : iniparserdir) endif iniparser = declare_dependency(include_directories: iniparserdir, dependencies:iniparser) conf = configuration_data() check_headers = [ ['HAVE_DLFCN_H', 'dlfcn.h'], ['HAVE_INTTYPES_H', 'inttypes.h'], ['HAVE_KEYUTILS_H', 'keyutils.h'], ['HAVE_LINUX_VERSION_H', 'linux/version.h'], ['HAVE_MEMORY_H', 'memory.h'], ['HAVE_STDINT_H', 'stdint.h'], ['HAVE_STDLIB_H', 'stdlib.h'], ['HAVE_STRINGS_H', 'strings.h'], ['HAVE_STRING_H', 'string.h'], ['HAVE_SYS_STAT_H', 'sys/stat.h'], ['HAVE_SYS_TYPES_H', 'sys/types.h'], ['HAVE_UNISTD_H', 'unistd.h'], ] foreach h : check_headers if cc.has_header(h.get(1)) conf.set(h.get(0), 1) endif endforeach map_sync_symbols = [ [ 'signal.h', 'BUS_MCEERR_AR' ], [ 'linux/mman.h', 'MAP_SHARED_VALIDATE' ], [ 'linux/mman.h', 'MAP_SYNC' ], ] count = 0 foreach symbol : map_sync_symbols if cc.has_header_symbol(symbol[0], symbol[1]) conf.set('HAVE_DECL_@0@'.format(symbol[1].to_upper()), 1) count = count + 1 endif endforeach poison_enabled = false if get_option('poison').enabled() and count == 3 poison_enabled = true endif conf.set('ENABLE_POISON', poison_enabled) conf.set('ENABLE_KEYUTILS', get_option('keyutils').enabled()) conf.set('ENABLE_TEST', get_option('test').enabled()) conf.set('ENABLE_DESTRUCTIVE', get_option('destructive').enabled()) conf.set('ENABLE_LOGGING', get_option('logging').enabled()) conf.set('ENABLE_DEBUG', get_option('dbg').enabled()) conf.set('ENABLE_LIBTRACEFS', get_option('libtracefs').enabled()) typeof_code = ''' void func() { struct { char a[16]; } x; typeof(x) y; char static_assert[2 * (sizeof(x) == sizeof(y)) - 1]; } ''' if cc.compiles(typeof_code) conf.set('HAVE_TYPEOF', 1) conf.set('HAVE_STATEMENT_EXPR', 1) endif if target_machine.endian() == 'big' conf.set('HAVE_BIG_ENDIAN', 1) else conf.set('HAVE_LITTLE_ENDIAN', 1) endif conf.set('_GNU_SOURCE', true) conf.set_quoted('PREFIX', get_option('prefix')) conf.set_quoted('NDCTL_MAN_PATH', get_option('mandir')) foreach ident : ['secure_getenv', '__secure_getenv'] conf.set10('HAVE_' + ident.to_upper(), cc.has_function(ident)) endforeach conf.set10('HAVE_JSON_U64', cc.has_function('json_object_new_uint64', prefix : '''#include ''', dependencies : json, ) ) ndctlconf_dir = sysconfdir / 'ndctl.conf.d' ndctlconf = ndctlconf_dir / 'monitor.conf' conf.set_quoted('NDCTL_CONF_FILE', ndctlconf) conf.set_quoted('NDCTL_CONF_DIR', ndctlconf_dir) ndctlkeys_dir = sysconfdir / 'ndctl' / 'keys' conf.set_quoted('NDCTL_KEYS_DIR', ndctlkeys_dir) daxctlconf_dir = sysconfdir / 'daxctl.conf.d' daxctlconf = daxctlconf_dir / 'dax.conf' conf.set_quoted('DAXCTL_CONF_DIR', daxctlconf_dir) conf.set_quoted('DAXCTL_MODPROBE_DATA', datadir / 'daxctl/daxctl.conf') conf.set_quoted('DAXCTL_MODPROBE_INSTALL', sysconfdir / 'modprobe.d/daxctl.conf') config_h = configure_file( input : 'config.h.meson', output : 'config.h', configuration : conf ) add_project_arguments('-include', 'config.h', language : 'c') LIBNDCTL_CURRENT=27 LIBNDCTL_REVISION=4 LIBNDCTL_AGE=21 LIBDAXCTL_CURRENT=7 LIBDAXCTL_REVISION=1 LIBDAXCTL_AGE=6 LIBCXL_CURRENT=9 LIBCXL_REVISION=0 LIBCXL_AGE=8 root_inc = include_directories(['.', 'ndctl', ]) ccan = static_library('ccan', [ 'ccan/str/str.c', 'ccan/list/list.c' ], ) ccan_dep = declare_dependency(link_with : ccan) subdir('daxctl/lib') subdir('ndctl/lib') subdir('cxl/lib') subdir('util') subdir('ndctl') subdir('daxctl') subdir('cxl') if get_option('docs').enabled() subdir('Documentation/ndctl') subdir('Documentation/daxctl') subdir('Documentation/cxl') endif subdir('test') subdir('contrib') # only support spec file generation from git builds if version_tag == '' subdir('rhel') subdir('sles') endif ndctl-81/meson_options.txt000066400000000000000000000031731476737544500160770ustar00rootroot00000000000000option('version-tag', type : 'string', description : 'override the git version string') option('docs', type : 'feature', value : 'enabled') option('asciidoctor', type : 'feature', value : 'enabled') option('libtracefs', type : 'feature', value : 'enabled') option('systemd', type : 'feature', value : 'enabled') option('keyutils', type : 'feature', value : 'enabled', description : 'enable nvdimm device passphrase management') option('test', type : 'feature', value : 'disabled', description : 'enable shipping tests in ndctl') option('destructive', type : 'feature', value : 'disabled', description : 'enable tests that may clobber live system resources') option('poison', type : 'feature', value : 'enabled', description : 'enable tests that inject poison / memory-failure') option('logging', type : 'feature', value : 'enabled', description : 'enable log infrastructure') option('dbg', type : 'feature', value : 'enabled', description : 'enable dbg messages') option('rootprefix', type : 'string', description : '''override the root prefix [default '/' if split-usr and '/usr' otherwise]''') option('rootlibdir', type : 'string', description : '''[/usr]/lib/x86_64-linux-gnu or such''') option('pkgconfiglibdir', type : 'string', value : '', description : 'directory for standard pkg-config files') option('bashcompletiondir', type : 'string', description : '''${datadir}/bash-completion/completions''') option('iniparserdir', type : 'string', description : 'Path containing the iniparser header files') option('modprobedatadir', type : 'string', description : '''${sysconfdir}/modprobe.d/''') ndctl-81/ndctl.spec.in000066400000000000000000000163011476737544500150240ustar00rootroot00000000000000Name: ndctl Version: VERSION Release: 1%{?dist} Summary: Manage "libnvdimm" subsystem devices (Non-volatile Memory) License: GPL-2.0-only AND LGPL-2.1-only AND CC0-1.0 AND MIT Url: https://github.com/pmem/ndctl Source0: https://github.com/pmem/%{name}/archive/v%{version}.tar.gz#/%{name}-%{version}.tar.gz Requires: LNAME%{?_isa} = %{version}-%{release} Requires: DAX_LNAME%{?_isa} = %{version}-%{release} Requires: CXL_LNAME%{?_isa} = %{version}-%{release} BuildRequires: autoconf %if 0%{?rhel} && 0%{?rhel} < 9 BuildRequires: asciidoc %define asciidoctor -Dasciidoctor=disabled %define libtracefs -Dlibtracefs=disabled %else BuildRequires: rubygem-asciidoctor BuildRequires: libtraceevent-devel BuildRequires: libtracefs-devel %define asciidoctor -Dasciidoctor=enabled %define libtracefs -Dlibtracefs=enabled %endif BuildRequires: xmlto BuildRequires: automake BuildRequires: libtool BuildRequires: pkgconfig BuildRequires: pkgconfig(libkmod) BuildRequires: pkgconfig(libudev) BuildRequires: pkgconfig(uuid) BuildRequires: pkgconfig(json-c) BuildRequires: pkgconfig(bash-completion) BuildRequires: pkgconfig(systemd) BuildRequires: keyutils-libs-devel BuildRequires: systemd-rpm-macros BuildRequires: iniparser-devel BuildRequires: meson %description Utility library for managing the "libnvdimm" subsystem. The "libnvdimm" subsystem defines a kernel device model and control message interface for platform NVDIMM resources like those defined by the ACPI 6+ NFIT (NVDIMM Firmware Interface Table). %if 0%{?flatpak} %global _udevrulesdir %{_prefix}/lib/udev/rules.d %endif %package -n DNAME Summary: Development files for libndctl License: LGPL-2.1-only Requires: LNAME%{?_isa} = %{version}-%{release} %description -n DNAME The %{name}-devel package contains libraries and header files for developing applications that use %{name}. %package -n daxctl Summary: Manage Device-DAX instances License: GPL-2.0-only Requires: DAX_LNAME%{?_isa} = %{version}-%{release} %description -n daxctl The daxctl utility provides enumeration and provisioning commands for the Linux kernel Device-DAX facility. This facility enables DAX mappings of performance / feature differentiated memory without need of a filesystem. %package -n cxl-cli Summary: Manage CXL devices License: GPL-2.0-only Requires: CXL_LNAME%{?_isa} = %{version}-%{release} %description -n cxl-cli The cxl utility provides enumeration and provisioning commands for the Linux kernel CXL devices. %package -n CXL_DNAME Summary: Development files for libcxl License: LGPL-2.1-only Requires: CXL_LNAME%{?_isa} = %{version}-%{release} %description -n CXL_DNAME This package contains libraries and header files for developing applications that use libcxl, a library for enumerating and communicating with CXL devices. %package -n DAX_DNAME Summary: Development files for libdaxctl License: LGPL-2.1-only Requires: DAX_LNAME%{?_isa} = %{version}-%{release} %description -n DAX_DNAME The %{name}-devel package contains libraries and header files for developing applications that use %{name}, a library for enumerating "Device DAX" devices. Device DAX is a facility for establishing DAX mappings of performance / feature-differentiated memory. %package -n LNAME Summary: Management library for "libnvdimm" subsystem devices (Non-volatile Memory) License: LGPL-2.1-only AND CC0-1.0 AND MIT Requires: DAX_LNAME%{?_isa} = %{version}-%{release} %description -n LNAME Libraries for %{name}. %package -n DAX_LNAME Summary: Management library for "Device DAX" devices License: LGPL-2.1-only AND CC0-1.0 AND MIT %description -n DAX_LNAME Device DAX is a facility for establishing DAX mappings of performance / feature-differentiated memory. DAX_LNAME provides an enumeration / control API for these devices. %package -n CXL_LNAME Summary: Management library for CXL devices License: LGPL-2.1-only AND CC0-1.0 AND MIT %description -n CXL_LNAME libcxl is a library for enumerating and communicating with CXL devices. %prep %setup -q ndctl-%{version} %build %meson %{?asciidoctor} %{?libtracefs} -Dversion-tag=%{version} %meson_build %install %meson_install %check %meson_test %ldconfig_scriptlets -n LNAME %ldconfig_scriptlets -n DAX_LNAME %ldconfig_scriptlets -n CXL_LNAME %define bashcompdir %(pkg-config --variable=completionsdir bash-completion) %pre if [ -f %{_sysconfdir}/ndctl/monitor.conf ] ; then if ! [ -f %{_sysconfdir}/ndctl.conf.d/monitor.conf ] ; then cp -a %{_sysconfdir}/ndctl/monitor.conf /var/run/ndctl-monitor.conf-migration fi fi %post if [ -f /var/run/ndctl-monitor.conf-migration ] ; then config_found=false while read line ; do [ -n "$line" ] || continue case "$line" in \#*) continue ;; esac config_found=true break done < /var/run/ndctl-monitor.conf-migration if $config_found ; then echo "[monitor]" > %{_sysconfdir}/ndctl.conf.d/monitor.conf cat /var/run/ndctl-monitor.conf-migration >> %{_sysconfdir}/ndctl.conf.d/monitor.conf fi rm /var/run/ndctl-monitor.conf-migration fi %files %defattr(-,root,root) %license LICENSES/preferred/GPL-2.0 LICENSES/other/MIT LICENSES/other/CC0-1.0 %{_bindir}/ndctl %{_mandir}/man1/ndctl* %{bashcompdir}/ndctl %{_unitdir}/ndctl-monitor.service %dir %{_sysconfdir}/ndctl %dir %{_sysconfdir}/ndctl/keys %{_sysconfdir}/ndctl/keys/keys.readme %{_sysconfdir}/modprobe.d/nvdimm-security.conf %dir %{_sysconfdir}/ndctl.conf.d %config(noreplace) %{_sysconfdir}/ndctl.conf.d/monitor.conf %config(noreplace) %{_sysconfdir}/ndctl.conf.d/ndctl.conf %files -n daxctl %defattr(-,root,root) %license LICENSES/preferred/GPL-2.0 LICENSES/other/MIT LICENSES/other/CC0-1.0 %{_bindir}/daxctl %{_mandir}/man1/daxctl* %{_datadir}/daxctl %{bashcompdir}/daxctl %{_unitdir}/daxdev-reconfigure@.service %config %{_udevrulesdir}/90-daxctl-device.rules %dir %{_sysconfdir}/daxctl.conf.d/ %config(noreplace) %{_sysconfdir}/daxctl.conf.d/daxctl.example.conf %files -n cxl-cli %defattr(-,root,root) %license LICENSES/preferred/GPL-2.0 LICENSES/other/MIT LICENSES/other/CC0-1.0 %{_bindir}/cxl %{_mandir}/man1/cxl* %{bashcompdir}/cxl %{_unitdir}/cxl-monitor.service %files -n LNAME %defattr(-,root,root) %doc README.md %license LICENSES/preferred/LGPL-2.1 LICENSES/other/MIT LICENSES/other/CC0-1.0 %{_libdir}/libndctl.so.* %files -n DAX_LNAME %defattr(-,root,root) %doc README.md %license LICENSES/preferred/LGPL-2.1 LICENSES/other/MIT LICENSES/other/CC0-1.0 %{_libdir}/libdaxctl.so.* %files -n CXL_LNAME %defattr(-,root,root) %doc README.md %license LICENSES/preferred/LGPL-2.1 LICENSES/other/MIT LICENSES/other/CC0-1.0 %{_libdir}/libcxl.so.* %files -n DNAME %defattr(-,root,root) %license LICENSES/preferred/LGPL-2.1 %{_includedir}/ndctl/ %{_libdir}/libndctl.so %{_libdir}/pkgconfig/libndctl.pc %files -n DAX_DNAME %defattr(-,root,root) %license LICENSES/preferred/LGPL-2.1 %{_includedir}/daxctl/ %{_libdir}/libdaxctl.so %{_libdir}/pkgconfig/libdaxctl.pc %files -n CXL_DNAME %defattr(-,root,root) %license LICENSES/preferred/LGPL-2.1 %{_includedir}/cxl/ %{_libdir}/libcxl.so %{_libdir}/pkgconfig/libcxl.pc %{_mandir}/man3/cxl* %{_mandir}/man3/libcxl.3* %changelog * Fri May 27 2016 Dan Williams - 53-1 - add daxctl-libs + daxctl-devel packages - add bash completion * Mon Apr 04 2016 Dan Williams - 52-1 - Initial rpm submission to Fedora ndctl-81/ndctl/000077500000000000000000000000001476737544500135425ustar00rootroot00000000000000ndctl-81/ndctl/action.h000066400000000000000000000006331476737544500151720ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (C) 2015-2020 Intel Corporation. All rights reserved. */ #ifndef __NDCTL_ACTION_H__ #define __NDCTL_ACTION_H__ enum device_action { ACTION_ENABLE, ACTION_DISABLE, ACTION_CREATE, ACTION_DESTROY, ACTION_CHECK, ACTION_WAIT, ACTION_START, ACTION_CLEAR, ACTION_ACTIVATE, ACTION_READ_INFOBLOCK, ACTION_WRITE_INFOBLOCK, }; #endif /* __NDCTL_ACTION_H__ */ ndctl-81/ndctl/bat.c000066400000000000000000000022161476737544500144550ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2014-2020 Intel Corporation. All rights reserved. #include #include #include #include #include int cmd_bat(int argc, const char **argv, struct ndctl_ctx *ctx) { int loglevel = LOG_DEBUG, i, rc; struct ndctl_test *test; bool force = false; const char * const u[] = { "ndctl bat []", NULL }; const struct option options[] = { OPT_INTEGER('l', "loglevel", &loglevel, "set the log level (default LOG_DEBUG)"), OPT_BOOLEAN('f', "force", &force, "force run all tests regardless of required kernel"), OPT_END(), }; argc = parse_options(argc, argv, options, u, 0); for (i = 0; i < argc; i++) error("unknown parameter \"%s\"\n", argv[i]); if (argc) usage_with_options(u, options); if (force) test = ndctl_test_new(UINT_MAX); else test = ndctl_test_new(0); if (!test) { fprintf(stderr, "failed to initialize test\n"); return EXIT_FAILURE; } rc = test_pmem_namespaces(loglevel, test, ctx); fprintf(stderr, "test_pmem_namespaces: %s\n", rc ? "FAIL" : "PASS"); return ndctl_test_result(test, rc); } ndctl-81/ndctl/builtin.h000066400000000000000000000054741476737544500153730ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (C) 2015-2020 Intel Corporation. All rights reserved. */ #ifndef _NDCTL_BUILTIN_H_ #define _NDCTL_BUILTIN_H_ struct ndctl_ctx; int cmd_create_nfit(int argc, const char **argv, struct ndctl_ctx *ctx); int cmd_enable_namespace(int argc, const char **argv, struct ndctl_ctx *ctx); int cmd_create_namespace(int argc, const char **argv, struct ndctl_ctx *ctx); int cmd_destroy_namespace(int argc, const char **argv, struct ndctl_ctx *ctx); int cmd_read_infoblock(int argc, const char **argv, struct ndctl_ctx *ctx); int cmd_write_infoblock(int argc, const char **argv, struct ndctl_ctx *ctx); int cmd_disable_namespace(int argc, const char **argv, struct ndctl_ctx *ctx); int cmd_check_namespace(int argc, const char **argv, struct ndctl_ctx *ctx); int cmd_clear_errors(int argc, const char **argv, struct ndctl_ctx *ctx); int cmd_enable_region(int argc, const char **argv, struct ndctl_ctx *ctx); int cmd_disable_region(int argc, const char **argv, struct ndctl_ctx *ctx); int cmd_enable_dimm(int argc, const char **argv, struct ndctl_ctx *ctx); int cmd_disable_dimm(int argc, const char **argv, struct ndctl_ctx *ctx); int cmd_zero_labels(int argc, const char **argv, struct ndctl_ctx *ctx); int cmd_read_labels(int argc, const char **argv, struct ndctl_ctx *ctx); int cmd_write_labels(int argc, const char **argv, struct ndctl_ctx *ctx); int cmd_init_labels(int argc, const char **argv, struct ndctl_ctx *ctx); int cmd_check_labels(int argc, const char **argv, struct ndctl_ctx *ctx); int cmd_inject_error(int argc, const char **argv, struct ndctl_ctx *ctx); int cmd_wait_scrub(int argc, const char **argv, struct ndctl_ctx *ctx); int cmd_activate_firmware(int argc, const char **argv, struct ndctl_ctx *ctx); int cmd_start_scrub(int argc, const char **argv, struct ndctl_ctx *ctx); int cmd_list(int argc, const char **argv, struct ndctl_ctx *ctx); int cmd_monitor(int argc, const char **argv, struct ndctl_ctx *ctx); #ifdef ENABLE_TEST int cmd_test(int argc, const char **argv, struct ndctl_ctx *ctx); #endif #ifdef ENABLE_DESTRUCTIVE int cmd_bat(int argc, const char **argv, struct ndctl_ctx *ctx); #endif int cmd_update_firmware(int argc, const char **argv, struct ndctl_ctx *ctx); int cmd_inject_smart(int argc, const char **argv, struct ndctl_ctx *ctx); int cmd_setup_passphrase(int argc, const char **argv, struct ndctl_ctx *ctx); int cmd_update_passphrase(int argc, const char **argv, struct ndctl_ctx *ctx); int cmd_remove_passphrase(int argc, const char **argv, struct ndctl_ctx *ctx); int cmd_freeze_security(int argc, const char **argv, struct ndctl_ctx *ctx); int cmd_sanitize_dimm(int argc, const char **argv, struct ndctl_ctx *ctx); int cmd_load_keys(int argc, const char **argv, struct ndctl_ctx *ctx); int cmd_wait_overwrite(int argc, const char **argv, struct ndctl_ctx *ctx); #endif /* _NDCTL_BUILTIN_H_ */ ndctl-81/ndctl/bus.c000066400000000000000000000146111476737544500145020ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) 2015-2020 Intel Corporation. All rights reserved. */ #include #include #include #include #include "action.h" #include #include #include #include #include #include #include #include "filter.h" #include "json.h" static struct { bool verbose; bool force; bool idle; bool dryrun; unsigned int poll_interval; } param = { .idle = true, }; #define BASE_OPTIONS() \ OPT_BOOLEAN('v',"verbose", ¶m.verbose, "turn on debug") #define WAIT_OPTIONS() \ OPT_UINTEGER('p', "poll", ¶m.poll_interval, "poll interval (seconds)") #define ACTIVATE_OPTIONS() \ OPT_BOOLEAN('I', "idle", ¶m.idle, \ "allow platform-injected idle over activate (default)"), \ OPT_BOOLEAN('f', "force", ¶m.force, "try to force live activation"), \ OPT_BOOLEAN('n', "dry-run", ¶m.dryrun, \ "perform all setup/validation steps, skip the activate") static const struct option start_options[] = { BASE_OPTIONS(), OPT_END(), }; static const struct option wait_options[] = { BASE_OPTIONS(), WAIT_OPTIONS(), OPT_END(), }; static const struct option activate_options[] = { BASE_OPTIONS(), ACTIVATE_OPTIONS(), OPT_END(), }; static int activate_firmware(struct ndctl_bus *bus) { const char *provider = ndctl_bus_get_provider(bus); const char *devname = ndctl_bus_get_devname(bus); enum ndctl_fwa_method method; bool do_clear_noidle = false; enum ndctl_fwa_state state; struct ndctl_dimm *dimm; bool has_fwupd = false; int rc; ndctl_dimm_foreach(bus, dimm) { rc = ndctl_dimm_fw_update_supported(dimm); if (rc == 0) { has_fwupd = true; break; } } if (!has_fwupd) { fprintf(stderr, "%s: %s: has no devices that support firmware update.\n", provider, devname); return -EOPNOTSUPP; } method = ndctl_bus_get_fw_activate_method(bus); if (method == NDCTL_FWA_METHOD_RESET) { fprintf(stderr, "%s: %s: requires a platform reset to activate firmware\n", provider, devname); return -EOPNOTSUPP; } if (!param.idle) { rc = ndctl_bus_set_fw_activate_noidle(bus); if (rc) { fprintf(stderr, "%s: %s: failed to disable platform idling.\n", provider, devname); /* not fatal, continue... */ } do_clear_noidle = true; } if (method == NDCTL_FWA_METHOD_SUSPEND && param.force) method = NDCTL_FWA_METHOD_LIVE; rc = 0; if (!param.dryrun) { state = ndctl_bus_get_fw_activate_state(bus); if (state != NDCTL_FWA_ARMED && state != NDCTL_FWA_ARM_OVERFLOW) { fprintf(stderr, "%s: %s: no devices armed\n", provider, devname); rc = -ENXIO; goto out; } rc = ndctl_bus_activate_firmware(bus, method); } if (rc) { fprintf(stderr, "%s: %s: firmware activation failed (%s)\n", provider, devname, strerror(-rc)); goto out; } out: if (do_clear_noidle) ndctl_bus_clear_fw_activate_noidle(bus); return rc; } static int scrub_action(struct ndctl_bus *bus, enum device_action action) { switch (action) { case ACTION_WAIT: return ndctl_bus_poll_scrub_completion(bus, param.poll_interval, 0); case ACTION_START: return ndctl_bus_start_scrub(bus); default: return -EINVAL; } } static void collect_result(struct json_object *jbuses, struct ndctl_bus *bus, enum device_action action) { unsigned long flags = UTIL_JSON_FIRMWARE | UTIL_JSON_HUMAN; struct json_object *jbus, *jdimms; struct ndctl_dimm *dimm; jbus = util_bus_to_json(bus, flags); if (jbus) json_object_array_add(jbuses, jbus); if (action != ACTION_ACTIVATE) return; jdimms = json_object_new_array(); if (!jdimms) return; ndctl_dimm_foreach(bus, dimm) { struct json_object *jdimm; jdimm = util_dimm_to_json(dimm, flags); if (jdimm) json_object_array_add(jdimms, jdimm); } if (json_object_array_length(jdimms) > 0) json_object_object_add(jbus, "dimms", jdimms); else json_object_put(jdimms); } static int bus_action(int argc, const char **argv, const char *usage, const struct option *options, enum device_action action, struct ndctl_ctx *ctx) { const char * const u[] = { usage, NULL }; int i, rc, success = 0, fail = 0; struct json_object *jbuses; struct ndctl_bus *bus; const char *all = "all"; argc = parse_options(argc, argv, options, u, 0); if (param.verbose) ndctl_set_log_priority(ctx, LOG_DEBUG); if (argc == 0) { argc = 1; argv = &all; } else for (i = 0; i < argc; i++) if (strcmp(argv[i], "all") == 0) { argv[0] = "all"; argc = 1; break; } jbuses = json_object_new_array(); if (!jbuses) return -ENOMEM; for (i = 0; i < argc; i++) { int found = 0; ndctl_bus_foreach(ctx, bus) { if (!util_bus_filter(bus, argv[i])) continue; found++; switch (action) { case ACTION_WAIT: case ACTION_START: rc = scrub_action(bus, action); break; case ACTION_ACTIVATE: rc = activate_firmware(bus); break; default: rc = -EINVAL; } if (rc == 0) { success++; collect_result(jbuses, bus, action); } else if (!fail) fail = rc; } if (!found && param.verbose) fprintf(stderr, "no bus matches id: %s\n", argv[i]); } if (success) util_display_json_array(stdout, jbuses, UTIL_JSON_FIRMWARE); else json_object_put(jbuses); if (success) return success; return fail ? fail : -ENXIO; } int cmd_start_scrub(int argc, const char **argv, struct ndctl_ctx *ctx) { char *usage = "ndctl start-scrub [ ... ] []"; int start = bus_action(argc, argv, usage, start_options, ACTION_START, ctx); if (start <= 0) { fprintf(stderr, "error starting scrub: %s\n", strerror(-start)); return start; } else { return 0; } } int cmd_wait_scrub(int argc, const char **argv, struct ndctl_ctx *ctx) { char *usage = "ndctl wait-scrub [ ... ] []"; int wait = bus_action(argc, argv, usage, wait_options, ACTION_WAIT, ctx); if (wait <= 0) { fprintf(stderr, "error waiting for scrub completion: %s\n", strerror(-wait)); return wait; } else { return 0; } } int cmd_activate_firmware(int argc, const char **argv, struct ndctl_ctx *ctx) { char *usage = "ndctl activate-firmware[ ... ] []"; int rc = bus_action(argc, argv, usage, activate_options, ACTION_ACTIVATE, ctx); if (rc <= 0) { fprintf(stderr, "error activating firmware: %s\n", strerror(-rc)); return rc; } return 0; } ndctl-81/ndctl/check.c000066400000000000000000001025061476737544500147670ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2015-2020 Intel Corporation. All rights reserved. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct check_opts { bool verbose; bool force; bool repair; bool logfix; }; struct btt_chk { char *path; int fd; uuid_t parent_uuid; unsigned long long rawsize; unsigned long long nlba; int start_off; int num_arenas; long sys_page_size; struct arena_info *arena; struct check_opts *opts; struct log_ctx ctx; }; struct arena_info { struct arena_map map; u64 size; /* Total bytes for this arena */ u64 external_lba_start; u32 internal_nlba; u32 internal_lbasize; u32 external_nlba; u32 external_lbasize; u32 nfree; u16 version_major; u16 version_minor; u64 nextoff; u64 infooff; u64 dataoff; u64 mapoff; u64 logoff; u64 info2off; u32 flags; int num; struct btt_chk *bttc; int log_index[2]; }; static sigjmp_buf sj_env; static void sigbus_hdl(int sig, siginfo_t *siginfo, void *ptr) { siglongjmp(sj_env, 1); } static int repair_msg(struct btt_chk *bttc) { info(bttc, " Run with --repair to make the changes\n"); return 0; } /** * btt_read_info - read an info block from a given offset * @bttc: the main btt_chk structure for this btt * @btt_sb: struct btt_sb where the info block will be copied into * @offset: offset in the raw namespace to read the info block from * * This will also use 'pread' to read the info block, and not mmap+loads * as this is used before the mappings are set up. */ static int btt_read_info(struct btt_chk *bttc, struct btt_sb *btt_sb, u64 off) { ssize_t size; size = pread(bttc->fd, btt_sb, sizeof(*btt_sb), off); if (size < 0) { err(bttc, "unable to read first info block: %s\n", strerror(errno)); return -errno; } if (size != sizeof(*btt_sb)) { err(bttc, "short read of first info block: %ld\n", size); return -ENXIO; } return 0; } /** * btt_write_info - write an info block to the given offset * @bttc: the main btt_chk structure for this btt * @btt_sb: struct btt_sb where the info block will be copied from * @offset: offset in the raw namespace to write the info block to * * This will also use 'pwrite' to write the info block, and not mmap+stores * as this is used before the mappings are set up. */ static int btt_write_info(struct btt_chk *bttc, struct btt_sb *btt_sb, u64 off) { ssize_t size; int rc; if (!bttc->opts->repair) { err(bttc, "BTT info block at offset %#lx needs to be restored\n", off); repair_msg(bttc); return -EIO; } info(bttc, "Restoring BTT info block at offset %#lx\n", off); size = pwrite(bttc->fd, btt_sb, sizeof(*btt_sb), off); if (size < 0) { err(bttc, "unable to write the info block: %s\n", strerror(errno)); return -errno; } if (size != sizeof(*btt_sb)) { err(bttc, "short write of the info block: %ld\n", size); return -ENXIO; } rc = fsync(bttc->fd); if (rc < 0) return -errno; return 0; } /** * btt_copy_to_info2 - restore the backup info block using the main one * @a: the arena_info handle for this arena * * Called when a corrupted backup info block is detected. Copies the * main info block over to the backup location. This is done using * mmap + stores, and thus needs a msync. */ static int btt_copy_to_info2(struct arena_info *a) { void *ms_align; size_t ms_size; if (!a->bttc->opts->repair) { err(a->bttc, "Arena %d: BTT info2 needs to be restored\n", a->num); return repair_msg(a->bttc); } printf("Arena %d: Restoring BTT info2\n", a->num); memcpy(a->map.info2, a->map.info, BTT_INFO_SIZE); ms_align = (void *)rounddown((u64)a->map.info2, a->bttc->sys_page_size); ms_size = max(BTT_INFO_SIZE, a->bttc->sys_page_size); if (msync(ms_align, ms_size, MS_SYNC) < 0) return -errno; return 0; } /* * btt_map_lookup - given a pre-map Arena Block Address, return the post-map ABA * @a: the arena_info handle for this arena * @lba: the logical block address for which we are performing the lookup * * This will correctly account for map entries in the 'initial state' */ static u32 btt_map_lookup(struct arena_info *a, u32 lba) { u32 raw_mapping; raw_mapping = le32_to_cpu(a->map.map[lba]); if (raw_mapping & MAP_ENT_NORMAL) return raw_mapping & MAP_LBA_MASK; else return lba; } static int btt_map_write(struct arena_info *a, u32 lba, u32 mapping) { void *ms_align; if (!a->bttc->opts->repair) { err(a->bttc, "Arena %d: map[%#x] needs to be updated to %#x\n", a->num, lba, mapping); return repair_msg(a->bttc); } info(a->bttc, "Arena %d: Updating map[%#x] to %#x\n", a->num, lba, mapping); /* * We want to set neither of the Z or E flags, and in the actual * layout, this means setting the bit positions of both to '1' to * indicate a 'normal' map entry */ mapping |= MAP_ENT_NORMAL; a->map.map[lba] = cpu_to_le32(mapping); ms_align = (void *)rounddown((u64)&a->map.map[lba], a->bttc->sys_page_size); if (msync(ms_align, a->bttc->sys_page_size, MS_SYNC) < 0) return -errno; return 0; } static void btt_log_group_read(struct arena_info *a, u32 lane, struct log_group *log) { memcpy(log, &a->map.log[lane], LOG_GRP_SIZE); } static void btt_log_group_write(struct arena_info *a, u32 lane, struct log_group *log) { memcpy(&a->map.log[lane], log, LOG_GRP_SIZE); } static u32 log_seq(struct log_group *log, int log_idx) { return le32_to_cpu(log->ent[log_idx].seq); } /* * This function accepts two log entries, and uses the sequence number to * find the 'older' entry. The return value indicates which of the two was * the 'old' entry */ static int btt_log_get_old(struct arena_info *a, struct log_group *log) { int idx0 = a->log_index[0]; int idx1 = a->log_index[1]; int old; if (log_seq(log, idx0) == 0) { log->ent[idx0].seq = cpu_to_le32(1); return 0; } if (log_seq(log, idx0) < log_seq(log, idx1)) { if ((log_seq(log, idx1) - log_seq(log, idx0)) == 1) old = 0; else old = 1; } else { if ((log_seq(log, idx0) - log_seq(log, idx1)) == 1) old = 1; else old = 0; } return old; } static int btt_log_read(struct arena_info *a, u32 lane, struct log_entry *ent) { int new_ent; struct log_group log; if (ent == NULL) return -EINVAL; btt_log_group_read(a, lane, &log); new_ent = 1 - btt_log_get_old(a, &log); memcpy(ent, &log.ent[a->log_index[new_ent]], LOG_ENT_SIZE); return 0; } /* * Never pass a mmapped buffer to this as it will attempt to write to * the buffer, and we want writes to only happened in a controlled fashion. * In the non --repair case, even if such a buffer is passed, the write will * result in a fault due to the readonly mmap flags. */ static int btt_info_verify(struct btt_chk *bttc, struct btt_sb *btt_sb) { if (memcmp(btt_sb->signature, BTT_SIG, BTT_SIG_LEN) != 0) return -ENXIO; if (!uuid_is_null(btt_sb->parent_uuid)) if (uuid_compare(bttc->parent_uuid, btt_sb->parent_uuid) != 0) return -ENXIO; if (!verify_infoblock_checksum((union info_block *) btt_sb)) return -ENXIO; return 0; } static int btt_info_read_verify(struct btt_chk *bttc, struct btt_sb *btt_sb, u64 off) { int rc; rc = btt_read_info(bttc, btt_sb, off); if (rc) return rc; rc = btt_info_verify(bttc, btt_sb); if (rc) return rc; return 0; } enum btt_errcodes { BTT_OK = 0, BTT_LOG_EQL_SEQ = 0x100, BTT_LOG_OOB_SEQ, BTT_LOG_OOB_LBA, BTT_LOG_OOB_OLD, BTT_LOG_OOB_NEW, BTT_LOG_MAP_ERR, BTT_MAP_OOB, BTT_BITMAP_ERROR, BTT_LOGFIX_ERR, }; static void btt_xlat_status(struct arena_info *a, int errcode) { switch(errcode) { case BTT_OK: break; case BTT_LOG_EQL_SEQ: err(a->bttc, "arena %d: found a pair of log entries with the same sequence number\n", a->num); break; case BTT_LOG_OOB_SEQ: err(a->bttc, "arena %d: found a log entry with an out of bounds sequence number\n", a->num); break; case BTT_LOG_OOB_LBA: err(a->bttc, "arena %d: found a log entry with an out of bounds LBA\n", a->num); break; case BTT_LOG_OOB_OLD: err(a->bttc, "arena %d: found a log entry with an out of bounds 'old' mapping\n", a->num); break; case BTT_LOG_OOB_NEW: err(a->bttc, "arena %d: found a log entry with an out of bounds 'new' mapping\n", a->num); break; case BTT_LOG_MAP_ERR: info(a->bttc, "arena %d: found a log entry that does not match with a map entry\n", a->num); break; case BTT_MAP_OOB: err(a->bttc, "arena %d: found a map entry that is out of bounds\n", a->num); break; case BTT_BITMAP_ERROR: err(a->bttc, "arena %d: bitmap error: internal blocks are incorrectly referenced\n", a->num); break; case BTT_LOGFIX_ERR: err(a->bttc, "arena %d: rewrite-log error: log may be in an unknown/unrecoverable state\n", a->num); break; default: err(a->bttc, "arena %d: unknown error: %d\n", a->num, errcode); } } /* Check that log entries are self consistent */ static int btt_check_log_entries(struct arena_info *a) { int idx0 = a->log_index[0]; int idx1 = a->log_index[1]; unsigned int i; int rc = 0; /* * First, check both 'slots' for sequence numbers being distinct * and in bounds */ for (i = 0; i < a->nfree; i++) { struct log_group *log = &a->map.log[i]; if (log_seq(log, idx0) == log_seq(log, idx1)) return BTT_LOG_EQL_SEQ; if (log_seq(log, idx0) > 3 || log_seq(log, idx1) > 3) return BTT_LOG_OOB_SEQ; } /* * Next, check only the 'new' slot in each lane for the remaining * fields being in bounds */ for (i = 0; i < a->nfree; i++) { struct log_entry ent; rc = btt_log_read(a, i, &ent); if (rc) return rc; if (ent.lba >= a->external_nlba) return BTT_LOG_OOB_LBA; if (ent.old_map >= a->internal_nlba) return BTT_LOG_OOB_OLD; if (ent.new_map >= a->internal_nlba) return BTT_LOG_OOB_NEW; } return rc; } /* Check that map entries are self consistent */ static int btt_check_map_entries(struct arena_info *a) { unsigned int i; u32 mapping; for (i = 0; i < a->external_nlba; i++) { mapping = btt_map_lookup(a, i); if (mapping >= a->internal_nlba) return BTT_MAP_OOB; } return 0; } /* Check that each flog entry has the correct corresponding map entry */ static int btt_check_log_map(struct arena_info *a) { unsigned int i; u32 mapping; int rc = 0, rc_saved = 0; for (i = 0; i < a->nfree; i++) { struct log_entry ent; rc = btt_log_read(a, i, &ent); if (rc) return rc; mapping = btt_map_lookup(a, ent.lba); /* * Case where the flog was written, but map couldn't be * updated. The kernel should also be able to detect and * fix this condition. */ if (ent.new_map != mapping && ent.old_map == mapping) { info(a->bttc, "arena %d: log[%d].new_map (%#x) doesn't match map[%#x] (%#x)\n", a->num, i, ent.new_map, ent.lba, mapping); rc = btt_map_write(a, ent.lba, ent.new_map); if (rc) rc_saved = rc; } } return rc_saved ? BTT_LOG_MAP_ERR : 0; } static int btt_check_info2(struct arena_info *a) { /* * Repair info2 if needed. The main info-block can be trusted * as it has been verified during arena discovery */ if(memcmp(a->map.info2, a->map.info, BTT_INFO_SIZE)) return btt_copy_to_info2(a); return 0; } /* * This will create a bitmap where each bit corresponds to an internal * 'block'. Between the BTT map and flog (representing 'free' blocks), * every single internal block must be represented exactly once. This * check will detect cases where either one or more blocks are never * referenced, or if a block is referenced more than once. */ static int btt_check_bitmap(struct arena_info *a) { unsigned long *bm; u32 i, btt_mapping; int rc = BTT_BITMAP_ERROR; bm = bitmap_alloc(a->internal_nlba); if (bm == NULL) return -ENOMEM; /* map 'external_nlba' number of map entries */ for (i = 0; i < a->external_nlba; i++) { btt_mapping = btt_map_lookup(a, i); if (test_bit(btt_mapping, bm)) { info(a->bttc, "arena %d: internal block %#x is referenced by two map entries\n", a->num, btt_mapping); goto out; } bitmap_set(bm, btt_mapping, 1); } /* map 'nfree' number of flog entries */ for (i = 0; i < a->nfree; i++) { struct log_entry ent; rc = btt_log_read(a, i, &ent); if (rc) goto out; if (test_bit(ent.old_map, bm)) { info(a->bttc, "arena %d: internal block %#x is referenced by two map/log entries\n", a->num, ent.old_map); rc = BTT_BITMAP_ERROR; goto out; } bitmap_set(bm, ent.old_map, 1); } /* check that the bitmap is full */ if (!bitmap_full(bm, a->internal_nlba)) rc = BTT_BITMAP_ERROR; out: free(bm); return rc; } static int btt_rewrite_log(struct arena_info *a) { struct log_group log; int rc; u32 i; info(a->bttc, "arena %d: rewriting log\n", a->num); /* * To rewrite the log, we implicitly use the 'new' padding scheme of * (0, 1) but resetting the log to a completely initial state (i.e. * slot-0 contains a made-up entry containing the 'free' block from * the existing current log entry, and a sequence number of '1'. All * other slots are zeroed. * * This way of rewriting the log is the most flexible as it can be * (ab)used to convert a new padding format back to the old one. * Since it only recreates slot-0, which is common between both * existing formats, an older kernel will simply initialize the free * list using those slot-0 entries, and run with it as though slot-2 * is the other valid slot. */ memset(&log, 0, LOG_GRP_SIZE); for (i = 0; i < a->nfree; i++) { struct log_entry ent; rc = btt_log_read(a, i, &ent); if (rc) return BTT_LOGFIX_ERR; log.ent[0].lba = ent.lba; log.ent[0].old_map = ent.old_map; log.ent[0].new_map = ent.new_map; log.ent[0].seq = 1; btt_log_group_write(a, i, &log); } return 0; } static int btt_check_arenas(struct btt_chk *bttc) { struct arena_info *a = NULL; int i, rc; for(i = 0; i < bttc->num_arenas; i++) { info(bttc, "checking arena %d\n", i); a = &bttc->arena[i]; rc = btt_check_log_entries(a); if (rc) break; rc = btt_check_map_entries(a); if (rc) break; rc = btt_check_log_map(a); if (rc) break; rc = btt_check_info2(a); if (rc) break; /* * bitmap test has to be after check_log_map so that any * pending log updates have been performed. Otherwise the * bitmap test may result in a false positive */ rc = btt_check_bitmap(a); if (rc) break; if (bttc->opts->logfix) { rc = btt_rewrite_log(a); if (rc) break; } } if (a && rc != BTT_OK) { btt_xlat_status(a, rc); return -ENXIO; } return 0; } /* * This copies over information from the info block to the arena_info struct. * The main difference is that all the offsets (infooff, mapoff etc) were * relative to the arena in the info block, but in arena_info, we use * arena_off to make these offsets absolute, i.e. relative to the start of * the raw namespace. */ static int btt_parse_meta(struct arena_info *arena, struct btt_sb *btt_sb, u64 arena_off) { arena->internal_nlba = le32_to_cpu(btt_sb->internal_nlba); arena->internal_lbasize = le32_to_cpu(btt_sb->internal_lbasize); arena->external_nlba = le32_to_cpu(btt_sb->external_nlba); arena->external_lbasize = le32_to_cpu(btt_sb->external_lbasize); arena->nfree = le32_to_cpu(btt_sb->nfree); if (arena->internal_nlba - arena->external_nlba != arena->nfree) return -ENXIO; if (arena->internal_lbasize != arena->external_lbasize) return -ENXIO; arena->version_major = le16_to_cpu(btt_sb->version_major); arena->version_minor = le16_to_cpu(btt_sb->version_minor); arena->nextoff = (btt_sb->nextoff == 0) ? 0 : (arena_off + le64_to_cpu(btt_sb->nextoff)); arena->infooff = arena_off; arena->dataoff = arena_off + le64_to_cpu(btt_sb->dataoff); arena->mapoff = arena_off + le64_to_cpu(btt_sb->mapoff); arena->logoff = arena_off + le64_to_cpu(btt_sb->logoff); arena->info2off = arena_off + le64_to_cpu(btt_sb->info2off); arena->size = (le64_to_cpu(btt_sb->nextoff) > 0) ? (le64_to_cpu(btt_sb->nextoff)) : (arena->info2off - arena->infooff + BTT_INFO_SIZE); arena->flags = le32_to_cpu(btt_sb->flags); if (btt_sb->flags & IB_FLAG_ERROR_MASK) { err(arena->bttc, "Info block error flag is set, aborting\n"); return -ENXIO; } return 0; } static bool ent_is_padding(struct log_entry *ent) { return (ent->lba == 0) && (ent->old_map == 0) && (ent->new_map == 0) && (ent->seq == 0); } /* * Detecting valid log indices: We read a log group, and iterate over its * four slots. We expect that a padding slot will be all-zeroes, and use this * to detect a padding slot vs. an actual entry. * * If a log_group is in the initial state, i.e. hasn't been used since the * creation of this BTT layout, it will have three of the four slots with * zeroes. We skip over these log_groups for the detection of log_index. If * all log_groups are in the initial state (i.e. the BTT has never been * written to), it is safe to assume the 'new format' of log entries in slots * (0, 1). */ static int log_set_indices(struct arena_info *arena) { bool idx_set = false, initial_state = true; int log_index[2] = {-1, -1}; struct log_group log; int j, next_idx = 0; u32 pad_count = 0; u32 i; for (i = 0; i < arena->nfree; i++) { btt_log_group_read(arena, i, &log); for (j = 0; j < 4; j++) { if (!idx_set) { if (ent_is_padding(&log.ent[j])) { pad_count++; continue; } else { /* Skip if index has been recorded */ if ((next_idx == 1) && (j == log_index[0])) continue; /* valid entry, record index */ log_index[next_idx] = j; next_idx++; } if (next_idx == 2) { /* two valid entries found */ idx_set = true; } else if (next_idx > 2) { /* too many valid indices */ return -ENXIO; } } else { /* * once the indices have been set, just verify * that all subsequent log groups are either in * their initial state or follow the same * indices. */ if (j == log_index[0]) { /* entry must be 'valid' */ if (ent_is_padding(&log.ent[j])) return -ENXIO; } else if (j == log_index[1]) { ; /* * log_index[1] can be padding if the * lane never got used and it is still * in the initial state (three 'padding' * entries) */ } else { /* entry must be invalid (padding) */ if (!ent_is_padding(&log.ent[j])) return -ENXIO; } } } /* * If any of the log_groups have more than one valid, * non-padding entry, then the we are no longer in the * initial_state */ if (pad_count < 3) initial_state = false; pad_count = 0; } if (!initial_state && !idx_set) return -ENXIO; /* * If all the entries in the log were in the initial state, * assume new padding scheme */ if (initial_state) log_index[1] = 1; /* * Only allow the known permutations of log/padding indices, * i.e. (0, 1), and (0, 2) */ if ((log_index[0] == 0) && ((log_index[1] == 1) || (log_index[1] == 2))) ; /* known index possibilities */ else { err(arena->bttc, "Found an unknown padding scheme\n"); return -ENXIO; } arena->log_index[0] = log_index[0]; arena->log_index[1] = log_index[1]; info(arena->bttc, "arena[%d]: log_index_0 = %d\n", arena->num, log_index[0]); info(arena->bttc, "arena[%d]: log_index_1 = %d\n", arena->num, log_index[1]); return 0; } static int btt_discover_arenas(struct btt_chk *bttc) { int ret = 0; struct arena_info *arena; struct btt_sb *btt_sb; size_t remaining = bttc->rawsize; size_t cur_off = bttc->start_off; u64 cur_nlba = 0; int i = 0; btt_sb = calloc(1, sizeof(*btt_sb)); if (!btt_sb) return -ENOMEM; while (remaining) { /* Alloc memory for arena */ arena = realloc(bttc->arena, (i + 1) * sizeof(*arena)); if (!arena) { ret = -ENOMEM; goto out; } else { bttc->arena = arena; arena = &bttc->arena[i]; /* zero the new memory */ memset(arena, 0, sizeof(*arena)); } arena->infooff = cur_off; ret = btt_read_info(bttc, btt_sb, cur_off); if (ret) goto out; if (btt_info_verify(bttc, btt_sb) != 0) { u64 offset; /* Try to find the backup info block */ if (remaining <= ARENA_MAX_SIZE) offset = rounddown(bttc->rawsize, SZ_4K) - BTT_INFO_SIZE; else offset = cur_off + ARENA_MAX_SIZE - BTT_INFO_SIZE; info(bttc, "Arena %d: Attempting recover info-block using info2\n", i); ret = btt_read_info(bttc, btt_sb, offset); if (ret) { err(bttc, "Unable to read backup info block (offset %#lx)\n", offset); goto out; } ret = btt_info_verify(bttc, btt_sb); if (ret) { err(bttc, "Backup info block (offset %#lx) verification failed\n", offset); goto out; } ret = btt_write_info(bttc, btt_sb, cur_off); if (ret) { err(bttc, "Restoration of the info block failed: %s (%d)\n", strerror(abs(ret)), ret); goto out; } } arena->num = i; arena->bttc = bttc; arena->external_lba_start = cur_nlba; ret = btt_parse_meta(arena, btt_sb, cur_off); if (ret) { err(bttc, "Problem parsing arena[%d] metadata\n", i); goto out; } remaining -= arena->size; cur_off += arena->size; cur_nlba += arena->external_nlba; i++; if (arena->nextoff == 0) break; } bttc->num_arenas = i; bttc->nlba = cur_nlba; info(bttc, "found %d BTT arena%s\n", bttc->num_arenas, (bttc->num_arenas > 1) ? "s" : ""); free(btt_sb); return ret; out: free(bttc->arena); free(btt_sb); return ret; } /* * Wrap call to mmap(2) to work with btt device offsets that are not aligned * to system page boundary. It works by rounding down the requested offset * to sys_page_size when calling mmap(2) and then returning a fixed-up pointer * to the correct offset in the mmaped region. */ static void *btt_mmap(struct btt_chk *bttc, size_t length, off_t offset) { off_t page_offset; int prot_flags; uint8_t *addr; if (!bttc->opts->repair) prot_flags = PROT_READ; else prot_flags = PROT_READ|PROT_WRITE; /* Calculate the page_offset from the system page boundary */ page_offset = offset - rounddown(offset, bttc->sys_page_size); /* Update the offset and length with the page_offset calculated above */ offset -= page_offset; length += page_offset; addr = mmap(NULL, length, prot_flags, MAP_SHARED, bttc->fd, offset); /* If needed fixup the return pointer to correct offset requested */ if (addr != MAP_FAILED) addr += page_offset; dbg(bttc, "addr = %p, length = %#lx, offset = %#lx, page_offset = %#lx\n", (void *) addr, length, offset, page_offset); return addr == MAP_FAILED ? NULL : addr; } static void btt_unmap(struct btt_chk *bttc, void *ptr, size_t length) { off_t page_offset; uintptr_t addr = (uintptr_t) ptr; /* Calculate the page_offset from system page boundary */ page_offset = addr - rounddown(addr, bttc->sys_page_size); addr -= page_offset; length += page_offset; munmap((void *) addr, length); dbg(bttc, "addr = %p, length = %#lx, page_offset = %#lx\n", (void *) addr, length, page_offset); } static int btt_create_mappings(struct btt_chk *bttc) { struct arena_info *a; int i; for (i = 0; i < bttc->num_arenas; i++) { a = &bttc->arena[i]; a->map.info_len = BTT_INFO_SIZE; a->map.info = btt_mmap(bttc, a->map.info_len, a->infooff); if (!a->map.info) { err(bttc, "mmap arena[%d].info [sz = %#lx, off = %#lx] failed: %s\n", i, a->map.info_len, a->infooff, strerror(errno)); return -errno; } a->map.data_len = a->mapoff - a->dataoff; a->map.data = btt_mmap(bttc, a->map.data_len, a->dataoff); if (!a->map.data) { err(bttc, "mmap arena[%d].data [sz = %#lx, off = %#lx] failed: %s\n", i, a->map.data_len, a->dataoff, strerror(errno)); return -errno; } a->map.map_len = a->logoff - a->mapoff; a->map.map = btt_mmap(bttc, a->map.map_len, a->mapoff); if (!a->map.map) { err(bttc, "mmap arena[%d].map [sz = %#lx, off = %#lx] failed: %s\n", i, a->map.map_len, a->mapoff, strerror(errno)); return -errno; } a->map.log_len = a->info2off - a->logoff; a->map.log = btt_mmap(bttc, a->map.log_len, a->logoff); if (!a->map.log) { err(bttc, "mmap arena[%d].log [sz = %#lx, off = %#lx] failed: %s\n", i, a->map.log_len, a->logoff, strerror(errno)); return -errno; } a->map.info2_len = BTT_INFO_SIZE; a->map.info2 = btt_mmap(bttc, a->map.info2_len, a->info2off); if (!a->map.info2) { err(bttc, "mmap arena[%d].info2 [sz = %#lx, off = %#lx] failed: %s\n", i, a->map.info2_len, a->info2off, strerror(errno)); return -errno; } } return 0; } static void btt_remove_mappings(struct btt_chk *bttc) { struct arena_info *a; int i; for (i = 0; i < bttc->num_arenas; i++) { a = &bttc->arena[i]; if (a->map.info) btt_unmap(bttc, a->map.info, a->map.info_len); if (a->map.data) btt_unmap(bttc, a->map.data, a->map.data_len); if (a->map.map) btt_unmap(bttc, a->map.map, a->map.map_len); if (a->map.log) btt_unmap(bttc, a->map.log, a->map.log_len); if (a->map.info2) btt_unmap(bttc, a->map.info2, a->map.info2_len); } } static int btt_sb_get_expected_offset(struct btt_sb *btt_sb) { u16 version_major, version_minor; version_major = le16_to_cpu(btt_sb->version_major); version_minor = le16_to_cpu(btt_sb->version_minor); if (version_major == 1 && version_minor == 1) return BTT1_START_OFFSET; else if (version_major == 2 && version_minor == 0) return BTT2_START_OFFSET; else return -ENXIO; } static int __btt_recover_first_sb(struct btt_chk *bttc, int off) { int rc, est_arenas = 0; u64 offset, remaining; struct btt_sb *btt_sb; /* Estimate the number of arenas */ remaining = bttc->rawsize - off; while (remaining) { if (remaining < ARENA_MIN_SIZE && est_arenas == 0) return -EINVAL; if (remaining > ARENA_MAX_SIZE) { /* full-size arena */ remaining -= ARENA_MAX_SIZE; est_arenas++; continue; } if (remaining < ARENA_MIN_SIZE) { /* 'remaining' was too small for another arena */ break; } else { /* last, short arena */ remaining = 0; est_arenas++; break; } } info(bttc, "estimated arenas: %d, remaining bytes: %#lx\n", est_arenas, remaining); btt_sb = malloc(2 * sizeof(*btt_sb)); if (btt_sb == NULL) return -ENOMEM; /* Read the original first info block into btt_sb[0] */ rc = btt_read_info(bttc, &btt_sb[0], off); if (rc) goto out; /* Attepmt 1: try recovery from expected end of the first arena */ if (est_arenas == 1) offset = rounddown(bttc->rawsize - remaining, SZ_4K) - BTT_INFO_SIZE; else offset = ARENA_MAX_SIZE - BTT_INFO_SIZE + off; info(bttc, "Attempting recover info-block from end-of-arena offset %#lx\n", offset); rc = btt_info_read_verify(bttc, &btt_sb[1], offset); if (rc == 0) { int expected_offset = btt_sb_get_expected_offset(&btt_sb[1]); /* * The fact that the btt_sb is self-consistent doesn't tell us * what BTT version it was, if restoring from the end of the * arena. (i.e. a consistent sb may be found for any valid * start offset). Use the version information in the sb to * determine what the expected start offset is. */ if ((expected_offset < 0) || (expected_offset != off)) { rc = -ENXIO; goto out; } rc = btt_write_info(bttc, &btt_sb[1], off); goto out; } /* * Attempt 2: From the very end of 'rawsize', try to copy the fields * that are constant in every arena (only valid when multiple arenas * are present) */ if (est_arenas > 1) { offset = rounddown(bttc->rawsize - remaining, SZ_4K) - BTT_INFO_SIZE; info(bttc, "Attempting to recover info-block from end offset %#lx\n", offset); rc = btt_info_read_verify(bttc, &btt_sb[1], offset); if (rc) goto out; /* copy over the arena0 specific fields from btt_sb[0] */ btt_sb[1].flags = btt_sb[0].flags; btt_sb[1].external_nlba = btt_sb[0].external_nlba; btt_sb[1].internal_nlba = btt_sb[0].internal_nlba; btt_sb[1].nextoff = btt_sb[0].nextoff; btt_sb[1].dataoff = btt_sb[0].dataoff; btt_sb[1].mapoff = btt_sb[0].mapoff; btt_sb[1].logoff = btt_sb[0].logoff; btt_sb[1].info2off = btt_sb[0].info2off; btt_sb[1].checksum = btt_sb[0].checksum; rc = btt_info_verify(bttc, &btt_sb[1]); if (rc == 0) { rc = btt_write_info(bttc, &btt_sb[1], off); goto out; } } /* * Attempt 3: use info2off as-is, and check if we find a valid info * block at that location. */ offset = le32_to_cpu(btt_sb[0].info2off); if (offset > min(bttc->rawsize - BTT_INFO_SIZE, ARENA_MAX_SIZE - BTT_INFO_SIZE + off)) { rc = -ENXIO; goto out; } if (offset) { info(bttc, "Attempting to recover info-block from info2 offset %#lx\n", offset); rc = btt_info_read_verify(bttc, &btt_sb[1], offset + off); if (rc == 0) { rc = btt_write_info(bttc, &btt_sb[1], off); goto out; } } else rc = -ENXIO; out: free(btt_sb); return rc; } static int btt_recover_first_sb(struct btt_chk *bttc) { int offsets[BTT_NUM_OFFSETS] = { BTT1_START_OFFSET, BTT2_START_OFFSET, }; int i, rc; for (i = 0; i < BTT_NUM_OFFSETS; i++) { rc = __btt_recover_first_sb(bttc, offsets[i]); if (rc == 0) { bttc->start_off = offsets[i]; return rc; } } return rc; } int namespace_check(struct ndctl_namespace *ndns, bool verbose, bool force, bool repair, bool logfix) { const char *devname = ndctl_namespace_get_devname(ndns); struct check_opts __opts = { .verbose = verbose, .force = force, .repair = repair, .logfix = logfix, }, *opts = &__opts; int raw_mode, rc, disabled_flag = 0, open_flags; struct btt_sb *btt_sb; struct btt_chk *bttc; struct sigaction act; char path[50]; int i; bttc = calloc(1, sizeof(*bttc)); if (bttc == NULL) return -ENOMEM; log_init(&bttc->ctx, devname, "NDCTL_CHECK_NAMESPACE"); if (opts->verbose) bttc->ctx.log_priority = LOG_DEBUG; memset(&act, 0, sizeof(act)); act.sa_sigaction = sigbus_hdl; act.sa_flags = SA_SIGINFO; if (sigaction(SIGBUS, &act, 0)) { err(bttc, "Unable to set sigaction\n"); rc = -errno; goto out_bttc; } if (opts->logfix) { if (!opts->repair) { err(bttc, "--rewrite-log also requires --repair\n"); rc = -EINVAL; goto out_bttc; } info(bttc, "WARNING: interruption may cause unrecoverable metadata corruption\n"); } bttc->opts = opts; bttc->sys_page_size = sysconf(_SC_PAGESIZE); bttc->rawsize = ndctl_namespace_get_size(ndns); ndctl_namespace_get_uuid(ndns, bttc->parent_uuid); info(bttc, "checking %s\n", devname); if (ndctl_namespace_is_active(ndns)) { if (opts->force) { rc = ndctl_namespace_disable_safe(ndns); if (rc) goto out_bttc; disabled_flag = 1; } else { err(bttc, "%s: check aborted, namespace online\n", devname); rc = -EBUSY; goto out_bttc; } } /* In typical usage, the current raw_mode should be false. */ raw_mode = ndctl_namespace_get_raw_mode(ndns); /* * Putting the namespace into raw mode will allow us to access * the btt metadata. */ rc = ndctl_namespace_set_raw_mode(ndns, 1); if (rc < 0) { err(bttc, "%s: failed to set the raw mode flag: %s (%d)\n", devname, strerror(abs(rc)), rc); goto out_ns; } /* * Now enable the namespace. This will result in a pmem device * node showing up in /dev that is in raw mode. */ rc = ndctl_namespace_enable(ndns); if (rc != 0) { err(bttc, "%s: failed to enable in raw mode: %s (%d)\n", devname, strerror(abs(rc)), rc); goto out_ns; } sprintf(path, "/dev/%s", ndctl_namespace_get_block_device(ndns)); bttc->path = path; btt_sb = malloc(sizeof(*btt_sb)); if (btt_sb == NULL) { rc = -ENOMEM; goto out_ns; } if (!bttc->opts->repair) open_flags = O_RDONLY|O_EXCL; else open_flags = O_RDWR|O_EXCL; bttc->fd = open(bttc->path, open_flags); if (bttc->fd < 0) { err(bttc, "unable to open %s: %s\n", bttc->path, strerror(errno)); rc = -errno; goto out_sb; } /* * This is where we jump to if we receive a SIGBUS, prior to doing any * mmaped reads, and can safely abort */ if (sigsetjmp(sj_env, 1)) { err(bttc, "Received a SIGBUS\n"); err(bttc, "Metadata corruption found, recovery is not possible\n"); rc = -EFAULT; goto out_close; } /* Try reading a BTT1 info block first */ rc = btt_info_read_verify(bttc, btt_sb, BTT1_START_OFFSET); if (rc == 0) bttc->start_off = BTT1_START_OFFSET; if (rc) { /* Try reading a BTT2 info block */ rc = btt_info_read_verify(bttc, btt_sb, BTT2_START_OFFSET); if (rc == 0) bttc->start_off = BTT2_START_OFFSET; if (rc) { rc = btt_recover_first_sb(bttc); if (rc) { err(bttc, "Unable to recover any BTT info blocks\n"); err(bttc, "This may not be a sector mode namespace\n"); goto out_close; } /* * btt_recover_first_sb will have set bttc->start_off * based on the version it found */ rc = btt_info_read_verify(bttc, btt_sb, bttc->start_off); if (rc) goto out_close; } } rc = btt_discover_arenas(bttc); if (rc) goto out_close; rc = btt_create_mappings(bttc); if (rc) goto out_close; for (i = 0; i < bttc->num_arenas; i++) { rc = log_set_indices(&bttc->arena[i]); if (rc) { err(bttc, "Unable to deduce log/padding indices\n"); goto out_close; } } rc = btt_check_arenas(bttc); btt_remove_mappings(bttc); out_close: close(bttc->fd); out_sb: free(btt_sb); out_ns: ndctl_namespace_set_raw_mode(ndns, raw_mode); ndctl_namespace_disable_invalidate(ndns); if (disabled_flag) if(ndctl_namespace_enable(ndns) < 0) err(bttc, "%s: failed to re-enable namespace\n", devname); out_bttc: free(bttc); return rc; } ndctl-81/ndctl/create-nfit.c000066400000000000000000000100441476737544500161060ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright(C) 2014-2020 Intel Corporation. All rights reserved. #include #include #include #include #include #include #include #include #include #include #include #include #define DEFAULT_NFIT "local_nfit.bin" static const char *nfit_file = DEFAULT_NFIT; static LIST_HEAD(spas); struct spa { struct list_node list; unsigned long long size, offset; }; static int parse_add_spa(const struct option *option, const char *__arg, int unset) { struct spa *s = calloc(1, sizeof(struct spa)); char *arg = strdup(__arg); char *size, *offset; int rc = -ENOMEM; if (!s || !arg) goto err; rc = -EINVAL; size = arg; offset = strchr(arg, ','); if (!offset) goto err; *offset++ = '\0'; s->size = parse_size64(size); if (s->size == ULLONG_MAX) goto err; s->offset = parse_size64(offset); if (s->offset == ULLONG_MAX) goto err; list_add_tail(&spas, &s->list); free(arg); return 0; err: error("failed to parse --add-spa=%s\n", __arg); free(arg); free(s); return rc; } static struct nfit *create_nfit(struct list_head *spa_list) { struct nfit_spa *nfit_spa; struct nfit *nfit; struct spa *s; size_t size; char *buf; int i; size = sizeof(struct nfit); list_for_each(spa_list, s, list) size += sizeof(struct nfit_spa); buf = calloc(1, size); if (!buf) return NULL; /* nfit header */ nfit = (typeof(nfit)) buf; memcpy(nfit->h.signature, "NFIT", 4); writel(size, &nfit->h.length); writeb(1, &nfit->h.revision); memcpy(nfit->h.oemid, "LOCAL", 6); writew(1, &nfit->h.oem_tbl_id); writel(1, &nfit->h.oem_revision); writel(0x80860000, &nfit->h.asl_id); writel(1, &nfit->h.asl_revision); nfit_spa = (struct nfit_spa *) (buf + sizeof(*nfit)); i = 1; list_for_each(spa_list, s, list) { writew(NFIT_TABLE_SPA, &nfit_spa->type); writew(sizeof(*nfit_spa), &nfit_spa->length); nfit_spa_uuid_pm(&nfit_spa->type_uuid); writew(i++, &nfit_spa->range_index); writeq(s->offset, &nfit_spa->spa_base); writeq(s->size, &nfit_spa->spa_length); nfit_spa++; } writeb(acpi_checksum(buf, size), &nfit->h.checksum); return nfit; } static int write_nfit(struct nfit *nfit, const char *file, int force) { int fd; ssize_t rc; mode_t mode = S_IRUSR|S_IRGRP|S_IWUSR|S_IWGRP; fd = open(file, O_RDWR|O_CREAT|O_EXCL, mode); if (fd < 0 && !force && errno == EEXIST) { error("\"%s\" exists, overwrite with --force\n", file); return -EEXIST; } else if (fd < 0 && force && errno == EEXIST) { fd = open(file, O_RDWR|O_CREAT|O_TRUNC, mode); } if (fd < 0) { error("Failed to open \"%s\": %s\n", file, strerror(errno)); return -errno; } rc = write(fd, nfit, le32toh(nfit->h.length)); close(fd); return rc; } struct ndctl_ctx; int cmd_create_nfit(int argc, const char **argv, struct ndctl_ctx *ctx) { int i, rc = -ENXIO, force = 0; const char * const u[] = { "ndctl create-nfit []", NULL }; const struct option options[] = { OPT_CALLBACK('a', "add-spa", NULL, "size,offset", "add a system-physical-address range table entry", parse_add_spa), OPT_STRING('o', NULL, &nfit_file, "file", "output to (default: " DEFAULT_NFIT ")"), OPT_INCR('f', "force", &force, "overwrite if it already exists"), OPT_END(), }; struct spa *s, *_s; struct nfit *nfit = NULL; argc = parse_options(argc, argv, options, u, 0); for (i = 0; i < argc; i++) error("unknown parameter \"%s\"\n", argv[i]); if (list_empty(&spas)) error("specify at least one --add-spa= option\n"); if (argc || list_empty(&spas)) usage_with_options(u, options); nfit = create_nfit(&spas); if (!nfit) goto out; rc = write_nfit(nfit, nfit_file, force); if ((unsigned int) rc == le32toh(nfit->h.length)) { fprintf(stderr, "wrote %d bytes to %s\n", le32toh(nfit->h.length), nfit_file); rc = 0; } out: free(nfit); list_for_each_safe(&spas, s, _s, list) { list_del(&s->list); free(s); } return rc; } ndctl-81/ndctl/dimm.c000066400000000000000000001140561476737544500146430ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-2.1 // Copyright (C) 2016-2020, Intel Corporation. All rights reserved. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "filter.h" #include "json.h" #include "keys.h" static const char *cmd_name = "dimm"; static int err_count; #define err(fmt, ...) \ ({ err_count++; error("%s: " fmt, cmd_name, ##__VA_ARGS__); }) struct action_context { struct json_object *jdimms; enum ndctl_namespace_version labelversion; FILE *f_out; FILE *f_in; struct update_context update; }; static struct parameters { const char *bus; const char *outfile; const char *infile; const char *labelversion; const char *kek; unsigned len; unsigned offset; bool crypto_erase; bool overwrite; bool zero_key; bool master_pass; bool human; bool force; bool arm; bool arm_set; bool disarm; bool disarm_set; bool index; bool json; bool verbose; } param = { .arm = true, .labelversion = "1.1", }; static int action_disable(struct ndctl_dimm *dimm, struct action_context *actx) { if (ndctl_dimm_is_active(dimm)) { fprintf(stderr, "%s is active, skipping...\n", ndctl_dimm_get_devname(dimm)); return -EBUSY; } return ndctl_dimm_disable(dimm); } static int action_enable(struct ndctl_dimm *dimm, struct action_context *actx) { return ndctl_dimm_enable(dimm); } static int action_zero(struct ndctl_dimm *dimm, struct action_context *actx) { if (ndctl_dimm_is_active(dimm)) { fprintf(stderr, "%s: regions active, abort label write\n", ndctl_dimm_get_devname(dimm)); return -EBUSY; } return ndctl_dimm_zero_label_extent(dimm, param.len, param.offset); } static struct json_object *dump_label_json(struct ndctl_dimm *dimm, struct ndctl_cmd *cmd_read, ssize_t size, unsigned long flags) { struct json_object *jarray = json_object_new_array(); struct json_object *jlabel = NULL; struct namespace_label nslabel; unsigned int nsindex_size; unsigned int slot = 0; ssize_t offset; if (!jarray) return NULL; nsindex_size = ndctl_dimm_sizeof_namespace_index(dimm); if (nsindex_size == 0) return NULL; for (offset = nsindex_size * 2; offset < size; offset += ndctl_dimm_sizeof_namespace_label(dimm), slot++) { ssize_t len = min_t(ssize_t, ndctl_dimm_sizeof_namespace_label(dimm), size - offset); struct json_object *jobj; char uuid[40]; jlabel = json_object_new_object(); if (!jlabel) break; if (len < (ssize_t) ndctl_dimm_sizeof_namespace_label(dimm)) break; len = ndctl_cmd_cfg_read_get_data(cmd_read, &nslabel, len, offset); if (len < 0) break; if (le32_to_cpu(nslabel.slot) != slot) continue; uuid_unparse((void *) nslabel.uuid, uuid); jobj = json_object_new_string(uuid); if (!jobj) break; json_object_object_add(jlabel, "uuid", jobj); nslabel.name[NSLABEL_NAME_LEN - 1] = 0; jobj = json_object_new_string(nslabel.name); if (!jobj) break; json_object_object_add(jlabel, "name", jobj); jobj = json_object_new_int(le32_to_cpu(nslabel.slot)); if (!jobj) break; json_object_object_add(jlabel, "slot", jobj); jobj = json_object_new_int(le16_to_cpu(nslabel.position)); if (!jobj) break; json_object_object_add(jlabel, "position", jobj); jobj = json_object_new_int(le16_to_cpu(nslabel.nlabel)); if (!jobj) break; json_object_object_add(jlabel, "nlabel", jobj); jobj = util_json_object_hex(le32_to_cpu(nslabel.flags), flags); if (!jobj) break; json_object_object_add(jlabel, "flags", jobj); jobj = util_json_object_hex(le64_to_cpu(nslabel.isetcookie), flags); if (!jobj) break; json_object_object_add(jlabel, "isetcookie", jobj); jobj = util_json_new_u64(le64_to_cpu(nslabel.lbasize)); if (!jobj) break; json_object_object_add(jlabel, "lbasize", jobj); jobj = util_json_object_hex(le64_to_cpu(nslabel.dpa), flags); if (!jobj) break; json_object_object_add(jlabel, "dpa", jobj); jobj = util_json_object_size(le64_to_cpu(nslabel.rawsize), flags); if (!jobj) break; json_object_object_add(jlabel, "rawsize", jobj); json_object_array_add(jarray, jlabel); if (ndctl_dimm_sizeof_namespace_label(dimm) < 256) continue; uuid_unparse((void *) nslabel.type_guid, uuid); jobj = json_object_new_string(uuid); if (!jobj) break; json_object_object_add(jlabel, "type_guid", jobj); uuid_unparse((void *) nslabel.abstraction_guid, uuid); jobj = json_object_new_string(uuid); if (!jobj) break; json_object_object_add(jlabel, "abstraction_guid", jobj); } if (json_object_array_length(jarray) < 1) { json_object_put(jarray); if (jlabel) json_object_put(jlabel); jarray = NULL; } return jarray; } static struct json_object *dump_index_json(struct ndctl_dimm *dimm, struct ndctl_cmd *cmd_read, ssize_t size) { struct json_object *jarray = json_object_new_array(); struct json_object *jindex = NULL; struct namespace_index nsindex; unsigned int nsindex_size; ssize_t offset; if (!jarray) return NULL; nsindex_size = ndctl_dimm_sizeof_namespace_index(dimm); if (nsindex_size == 0) return NULL; for (offset = 0; offset < nsindex_size * 2; offset += nsindex_size) { ssize_t len = min_t(ssize_t, sizeof(nsindex), size - offset); struct json_object *jobj; jindex = json_object_new_object(); if (!jindex) break; if (len < (ssize_t) sizeof(nsindex)) break; len = ndctl_cmd_cfg_read_get_data(cmd_read, &nsindex, len, offset); if (len < 0) break; nsindex.sig[NSINDEX_SIG_LEN - 1] = 0; jobj = json_object_new_string(nsindex.sig); if (!jobj) break; json_object_object_add(jindex, "signature", jobj); jobj = json_object_new_int(le16_to_cpu(nsindex.major)); if (!jobj) break; json_object_object_add(jindex, "major", jobj); jobj = json_object_new_int(le16_to_cpu(nsindex.minor)); if (!jobj) break; json_object_object_add(jindex, "minor", jobj); jobj = json_object_new_int(1 << (7 + nsindex.labelsize)); if (!jobj) break; json_object_object_add(jindex, "labelsize", jobj); jobj = json_object_new_int(le32_to_cpu(nsindex.seq)); if (!jobj) break; json_object_object_add(jindex, "seq", jobj); jobj = json_object_new_int(le32_to_cpu(nsindex.nslot)); if (!jobj) break; json_object_object_add(jindex, "nslot", jobj); json_object_array_add(jarray, jindex); } if (json_object_array_length(jarray) < 1) { json_object_put(jarray); if (jindex) json_object_put(jindex); jarray = NULL; } return jarray; } static struct json_object *dump_json(struct ndctl_dimm *dimm, struct ndctl_cmd *cmd_read, ssize_t size) { unsigned long flags = param.human ? UTIL_JSON_HUMAN : 0; struct json_object *jdimm = json_object_new_object(); struct json_object *jlabel, *jobj, *jindex; if (!jdimm) return NULL; jobj = json_object_new_string(ndctl_dimm_get_devname(dimm)); if (!jobj) goto err; json_object_object_add(jdimm, "dev", jobj); jindex = dump_index_json(dimm, cmd_read, size); if (!jindex) goto err; json_object_object_add(jdimm, "index", jindex); if (param.index) return jdimm; jlabel = dump_label_json(dimm, cmd_read, size, flags); if (!jlabel) goto err; json_object_object_add(jdimm, "label", jlabel); return jdimm; err: json_object_put(jdimm); return NULL; } static int rw_bin(FILE *f, struct ndctl_cmd *cmd, ssize_t size, unsigned int start_offset, int rw) { char buf[4096]; ssize_t offset, write = 0; for (offset = start_offset; offset < start_offset + size; offset += sizeof(buf)) { ssize_t len = min_t(ssize_t, sizeof(buf), size - offset), rc; if (rw == WRITE) { len = fread(buf, 1, len, f); if (len == 0) break; rc = ndctl_cmd_cfg_write_set_data(cmd, buf, len, offset); if (rc < 0) return -ENXIO; write += len; } else { len = ndctl_cmd_cfg_read_get_data(cmd, buf, len, offset); if (len < 0) return len; rc = fwrite(buf, 1, len, f); if (rc != len) return -ENXIO; fflush(f); } } if (write) return ndctl_cmd_submit(cmd); return 0; } static int revalidate_labels(struct ndctl_dimm *dimm) { int rc; /* * If the dimm is already disabled the kernel is not holding a cached * copy of the label space. */ if (!ndctl_dimm_is_enabled(dimm)) return 0; rc = ndctl_dimm_disable(dimm); if (rc) return rc; return ndctl_dimm_enable(dimm); } static int action_write(struct ndctl_dimm *dimm, struct action_context *actx) { struct ndctl_cmd *cmd_read, *cmd_write; ssize_t size; int rc = 0; if (ndctl_dimm_is_active(dimm)) { fprintf(stderr, "dimm is active, abort label write\n"); return -EBUSY; } cmd_read = ndctl_dimm_read_label_extent(dimm, param.len, param.offset); if (!cmd_read) return -EINVAL; cmd_write = ndctl_dimm_cmd_new_cfg_write(cmd_read); if (!cmd_write) { ndctl_cmd_unref(cmd_read); return -ENXIO; } size = ndctl_cmd_cfg_read_get_size(cmd_read); rc = rw_bin(actx->f_in, cmd_write, size, param.offset, WRITE); if (rc) goto out; rc = revalidate_labels(dimm); out: ndctl_cmd_unref(cmd_read); ndctl_cmd_unref(cmd_write); return rc; } static int action_read(struct ndctl_dimm *dimm, struct action_context *actx) { struct ndctl_cmd *cmd_read; ssize_t size; int rc = 0; if (param.index) cmd_read = ndctl_dimm_read_label_index(dimm); else cmd_read = ndctl_dimm_read_label_extent(dimm, param.len, param.offset); if (!cmd_read) return -EINVAL; size = ndctl_cmd_cfg_read_get_size(cmd_read); if (actx->jdimms) { struct json_object *jdimm = dump_json(dimm, cmd_read, size); if (jdimm) json_object_array_add(actx->jdimms, jdimm); else rc = -ENOMEM; } else rc = rw_bin(actx->f_out, cmd_read, size, param.offset, READ); ndctl_cmd_unref(cmd_read); return rc; } static int update_verify_input(struct action_context *actx) { int rc; struct stat st; rc = fstat(fileno(actx->f_in), &st); if (rc == -1) { rc = -errno; fprintf(stderr, "fstat failed: %s\n", strerror(errno)); return rc; } if (!S_ISREG(st.st_mode)) { fprintf(stderr, "Input not a regular file.\n"); return -EINVAL; } if (st.st_size == 0) { fprintf(stderr, "Input file size is 0.\n"); return -EINVAL; } actx->update.fw_size = st.st_size; return 0; } static int verify_fw_size(struct update_context *uctx) { struct fw_info *fw = &uctx->dimm_fw; if (uctx->fw_size > fw->store_size) { error("Firmware file size greater than DIMM store\n"); return -ENOSPC; } return 0; } static int submit_get_firmware_info(struct ndctl_dimm *dimm, struct action_context *actx) { const char *devname = ndctl_dimm_get_devname(dimm); struct update_context *uctx = &actx->update; struct fw_info *fw = &uctx->dimm_fw; struct ndctl_cmd *cmd; int rc; enum ND_FW_STATUS status; cmd = ndctl_dimm_cmd_new_fw_get_info(dimm); if (!cmd) return -ENXIO; rc = ndctl_cmd_submit(cmd); if (rc < 0) goto out; rc = -ENXIO; status = ndctl_cmd_fw_xlat_firmware_status(cmd); if (status != FW_SUCCESS) { err("%s: failed to retrieve firmware info", devname); goto out; } fw->store_size = ndctl_cmd_fw_info_get_storage_size(cmd); if (fw->store_size == UINT_MAX) goto out; fw->update_size = ndctl_cmd_fw_info_get_max_send_len(cmd); if (fw->update_size == UINT_MAX) goto out; fw->query_interval = ndctl_cmd_fw_info_get_query_interval(cmd); if (fw->query_interval == UINT_MAX) goto out; fw->max_query = ndctl_cmd_fw_info_get_max_query_time(cmd); if (fw->max_query == UINT_MAX) goto out; fw->run_version = ndctl_cmd_fw_info_get_run_version(cmd); if (fw->run_version == ULLONG_MAX) goto out; rc = verify_fw_size(uctx); out: ndctl_cmd_unref(cmd); return rc; } static int submit_abort_firmware(struct ndctl_dimm *dimm, struct action_context *actx) { struct update_context *uctx = &actx->update; struct ndctl_cmd *cmd; int rc; enum ND_FW_STATUS status; cmd = ndctl_dimm_cmd_new_fw_abort(uctx->start); if (!cmd) return -ENXIO; rc = ndctl_cmd_submit(cmd); if (rc < 0) goto out; status = ndctl_cmd_fw_xlat_firmware_status(cmd); if (status != FW_ABORTED) { fprintf(stderr, "Firmware update abort on DIMM %s failed: %#x\n", ndctl_dimm_get_devname(dimm), status); rc = -ENXIO; goto out; } out: ndctl_cmd_unref(cmd); return rc; } static int submit_start_firmware_upload(struct ndctl_dimm *dimm, struct action_context *actx) { const char *devname = ndctl_dimm_get_devname(dimm); struct update_context *uctx = &actx->update; struct fw_info *fw = &uctx->dimm_fw; struct ndctl_cmd *cmd; enum ND_FW_STATUS status; int rc; cmd = ndctl_dimm_cmd_new_fw_start_update(dimm); if (!cmd) return -ENXIO; rc = ndctl_cmd_submit(cmd); if (rc < 0) goto err; uctx->start = cmd; status = ndctl_cmd_fw_xlat_firmware_status(cmd); if (status == FW_EBUSY) { if (param.force) { rc = submit_abort_firmware(dimm, actx); if (rc < 0) { err("%s: busy with another firmware update, " "abort failed", devname); rc = -EBUSY; goto err; } rc = -EAGAIN; goto err; } else { err("%s: busy with another firmware update", devname); rc = -EBUSY; goto err; } } if (status != FW_SUCCESS) { err("%s: failed to create start context", devname); rc = -ENXIO; goto err; } fw->context = ndctl_cmd_fw_start_get_context(cmd); if (fw->context == UINT_MAX) { err("%s: failed to retrieve start context", devname); rc = -ENXIO; goto err; } return 0; err: uctx->start = NULL; ndctl_cmd_unref(cmd); return rc; } static int get_fw_data_from_file(FILE *file, void *buf, uint32_t len) { size_t rc; rc = fread(buf, len, 1, file); if (rc != 1) { if (feof(file)) fprintf(stderr, "Firmware file shorter than expected\n"); else if (ferror(file)) fprintf(stderr, "Firmware file read error\n"); return -EBADF; } return len; } static int send_firmware(struct ndctl_dimm *dimm, struct action_context *actx) { const char *devname = ndctl_dimm_get_devname(dimm); struct update_context *uctx = &actx->update; struct fw_info *fw = &uctx->dimm_fw; uint32_t copied = 0, len, remain; struct ndctl_cmd *cmd = NULL; enum ND_FW_STATUS status; int rc = -ENXIO; ssize_t read; void *buf; buf = malloc(fw->update_size); if (!buf) return -ENOMEM; remain = uctx->fw_size; while (remain) { len = min(fw->update_size, remain); read = get_fw_data_from_file(actx->f_in, buf, len); if (read < 0) { rc = read; goto cleanup; } cmd = ndctl_dimm_cmd_new_fw_send(uctx->start, copied, read, buf); if (!cmd) { rc = -ENOMEM; goto cleanup; } rc = ndctl_cmd_submit(cmd); if (rc < 0) { err("%s: failed to issue firmware transmit: %d", devname, rc); goto cleanup; } status = ndctl_cmd_fw_xlat_firmware_status(cmd); if (status != FW_SUCCESS) { err("%s: failed to transmit firmware: %d", devname, status); rc = -EIO; goto cleanup; } copied += read; remain -= read; ndctl_cmd_unref(cmd); cmd = NULL; } cleanup: ndctl_cmd_unref(cmd); free(buf); return rc; } static int submit_finish_firmware(struct ndctl_dimm *dimm, struct action_context *actx) { const char *devname = ndctl_dimm_get_devname(dimm); struct update_context *uctx = &actx->update; enum ND_FW_STATUS status; struct ndctl_cmd *cmd; int rc = -ENXIO; cmd = ndctl_dimm_cmd_new_fw_finish(uctx->start); if (!cmd) return -ENXIO; rc = ndctl_cmd_submit(cmd); if (rc < 0) goto out; status = ndctl_cmd_fw_xlat_firmware_status(cmd); switch (status) { case FW_SUCCESS: rc = 0; break; case FW_ERETRY: err("%s: device busy with other operation (ARS?)", devname); break; case FW_EBADFW: err("%s: firmware image rejected", devname); break; default: err("%s: update failed: error code: %d", devname, status); break; } out: ndctl_cmd_unref(cmd); return rc; } static enum ndctl_fwa_state fw_update_arm(struct ndctl_dimm *dimm) { struct ndctl_bus *bus = ndctl_dimm_get_bus(dimm); const char *devname = ndctl_dimm_get_devname(dimm); enum ndctl_fwa_state state = ndctl_bus_get_fw_activate_state(bus); if (state == NDCTL_FWA_INVALID) { if (param.verbose) err("%s: firmware activate capability not found\n", devname); return NDCTL_FWA_INVALID; } if (state == NDCTL_FWA_ARM_OVERFLOW && !param.force) { err("%s: overflow detected skip arm\n", devname); return NDCTL_FWA_INVALID; } state = ndctl_dimm_fw_activate_arm(dimm); if (state != NDCTL_FWA_ARMED) { err("%s: failed to arm\n", devname); return NDCTL_FWA_INVALID; } if (param.force) return state; state = ndctl_bus_get_fw_activate_state(bus); if (state == NDCTL_FWA_ARM_OVERFLOW) { err("%s: arm aborted, tripped overflow\n", devname); ndctl_dimm_fw_activate_disarm(dimm); return NDCTL_FWA_INVALID; } return NDCTL_FWA_ARMED; } #define ARM_FAILURE_FATAL (1) #define ARM_FAILURE_OK (0) static int fw_update_toggle_arm(struct ndctl_dimm *dimm, struct json_object *jdimms, bool arm_fatal) { enum ndctl_fwa_state state; struct json_object *jobj; unsigned long flags; if (param.disarm) state = ndctl_dimm_fw_activate_disarm(dimm); else if (param.arm) state = fw_update_arm(dimm); else state = NDCTL_FWA_INVALID; if (state == NDCTL_FWA_INVALID && arm_fatal) return -ENXIO; flags = UTIL_JSON_FIRMWARE; if (isatty(1)) flags |= UTIL_JSON_HUMAN; jobj = util_dimm_to_json(dimm, flags); if (jobj) json_object_array_add(jdimms, jobj); return 0; } static int query_fw_finish_status(struct ndctl_dimm *dimm, struct action_context *actx) { const char *devname = ndctl_dimm_get_devname(dimm); struct update_context *uctx = &actx->update; struct fw_info *fw = &uctx->dimm_fw; struct timespec now, before, after; enum ND_FW_STATUS status; struct ndctl_cmd *cmd; uint64_t ver; int rc; cmd = ndctl_dimm_cmd_new_fw_finish_query(uctx->start); if (!cmd) return -ENXIO; rc = clock_gettime(CLOCK_MONOTONIC, &before); if (rc < 0) goto unref; now.tv_nsec = fw->query_interval / 1000; now.tv_sec = 0; again: rc = ndctl_cmd_submit(cmd); if (rc < 0) goto unref; status = ndctl_cmd_fw_xlat_firmware_status(cmd); if (status == FW_EBUSY) { /* Still on going, continue */ rc = clock_gettime(CLOCK_MONOTONIC, &after); if (rc < 0) { rc = -errno; goto unref; } /* * If we expire max query time, * we timed out */ if (after.tv_sec - before.tv_sec > fw->max_query / 1000000) { rc = -ETIMEDOUT; goto unref; } /* * Sleep the interval dictated by firmware * before query again. */ rc = nanosleep(&now, NULL); if (rc < 0) { rc = -errno; goto unref; } goto again; } /* We are done determine error code */ switch (status) { case FW_SUCCESS: ver = ndctl_cmd_fw_fquery_get_fw_rev(cmd); if (ver == 0) { err("%s: new firmware not found after update", devname); rc = -EIO; goto unref; } /* * Now try to arm/disarm firmware activation if * requested. Failure to toggle the arm state is not * fatal, the success / failure will be inferred from * the emitted json state. */ fw_update_toggle_arm(dimm, actx->jdimms, ARM_FAILURE_OK); rc = 0; break; case FW_EBADFW: err("%s: firmware verification failure", devname); rc = -EINVAL; break; case FW_ENORES: err("%s: timeout awaiting update", devname); rc = -ETIMEDOUT; break; default: err("%s: unhandled error %d", devname, status); rc = -EIO; break; } unref: ndctl_cmd_unref(cmd); return rc; } static int update_firmware(struct ndctl_dimm *dimm, struct action_context *actx) { const char *devname = ndctl_dimm_get_devname(dimm); int rc, i; rc = submit_get_firmware_info(dimm, actx); if (rc < 0) return rc; /* try a few times in the --force and state busy case */ for (i = 0; i < 3; i++) { rc = submit_start_firmware_upload(dimm, actx); if (rc == -EAGAIN) continue; if (rc < 0) return rc; break; } if (param.verbose) fprintf(stderr, "%s: uploading firmware\n", devname); rc = send_firmware(dimm, actx); if (rc < 0) { err("%s: firmware send failed", devname); rc = submit_abort_firmware(dimm, actx); if (rc < 0) err("%s: abort failed", devname); return rc; } /* * Done reading file, reset firmware file back to beginning for * next update. */ rewind(actx->f_in); rc = submit_finish_firmware(dimm, actx); if (rc < 0) { err("%s: failed to finish update sequence", devname); rc = submit_abort_firmware(dimm, actx); if (rc < 0) err("%s: failed to abort update", devname); return rc; } rc = query_fw_finish_status(dimm, actx); if (rc < 0) return rc; return 0; } static int action_update(struct ndctl_dimm *dimm, struct action_context *actx) { struct ndctl_bus *bus = ndctl_dimm_get_bus(dimm); const char *devname = ndctl_dimm_get_devname(dimm); int rc; if (!param.infile) return fw_update_toggle_arm(dimm, actx->jdimms, ARM_FAILURE_FATAL); rc = ndctl_dimm_fw_update_supported(dimm); switch (rc) { case -ENOTTY: err("%s: firmware update not supported by ndctl.", devname); return rc; case -EOPNOTSUPP: err("%s: firmware update not supported by the kernel", devname); return rc; case -EIO: err("%s: firmware update not supported by either platform firmware or the kernel.", devname); return rc; } if (ndctl_bus_get_scrub_state(bus) > 0 && !param.force) { err("%s: scrub active, retry after 'ndctl wait-scrub'", devname); return -EBUSY; } rc = update_verify_input(actx); if (rc < 0) return rc; rc = update_firmware(dimm, actx); if (rc < 0) return rc; ndctl_cmd_unref(actx->update.start); return rc; } static int action_setup_passphrase(struct ndctl_dimm *dimm, struct action_context *actx) { if (ndctl_dimm_get_security(dimm) < 0) { error("%s: security operation not supported\n", ndctl_dimm_get_devname(dimm)); return -EOPNOTSUPP; } if (!param.kek) return -EINVAL; return ndctl_dimm_setup_key(dimm, param.kek, param.master_pass ? ND_MASTER_KEY : ND_USER_KEY); } static int action_update_passphrase(struct ndctl_dimm *dimm, struct action_context *actx) { if (ndctl_dimm_get_security(dimm) < 0) { error("%s: security operation not supported\n", ndctl_dimm_get_devname(dimm)); return -EOPNOTSUPP; } return ndctl_dimm_update_key(dimm, param.kek, param.master_pass ? ND_MASTER_KEY : ND_USER_KEY); } static int action_remove_passphrase(struct ndctl_dimm *dimm, struct action_context *actx) { if (ndctl_dimm_get_security(dimm) < 0) { error("%s: security operation not supported\n", ndctl_dimm_get_devname(dimm)); return -EOPNOTSUPP; } return ndctl_dimm_remove_key(dimm, param.master_pass ? ND_MASTER_KEY : ND_USER_KEY); } static int action_security_freeze(struct ndctl_dimm *dimm, struct action_context *actx) { int rc; if (ndctl_dimm_get_security(dimm) < 0) { error("%s: security operation not supported\n", ndctl_dimm_get_devname(dimm)); return -EOPNOTSUPP; } rc = ndctl_dimm_freeze_security(dimm); if (rc < 0) error("Failed to freeze security for %s\n", ndctl_dimm_get_devname(dimm)); return rc; } static int action_sanitize_dimm(struct ndctl_dimm *dimm, struct action_context *actx) { int rc = 0; enum ndctl_key_type key_type; if (ndctl_dimm_get_security(dimm) < 0) { error("%s: security operation not supported\n", ndctl_dimm_get_devname(dimm)); return -EOPNOTSUPP; } if (param.overwrite && param.master_pass) { error("%s: overwrite does not support master passphrase\n", ndctl_dimm_get_devname(dimm)); return -EINVAL; } /* * Setting crypto erase to be default. The other method will be * overwrite. */ if (!param.crypto_erase && !param.overwrite) { param.crypto_erase = true; if (param.verbose) fprintf(stderr, "No santize method passed in, default to crypto-erase\n"); } if (param.crypto_erase) { if (param.zero_key) key_type = ND_ZERO_KEY; else if (param.master_pass) key_type = ND_MASTER_KEY; else key_type = ND_USER_KEY; rc = ndctl_dimm_secure_erase_key(dimm, key_type); if (rc < 0) return rc; } if (param.overwrite) { rc = ndctl_dimm_overwrite_key(dimm); if (rc < 0) return rc; rc = revalidate_labels(dimm); } return rc; } static int action_wait_overwrite(struct ndctl_dimm *dimm, struct action_context *actx) { int rc; if (ndctl_dimm_get_security(dimm) < 0) { error("%s: security operation not supported\n", ndctl_dimm_get_devname(dimm)); return -EOPNOTSUPP; } rc = ndctl_dimm_wait_overwrite(dimm); if (rc == 1 && param.verbose) fprintf(stderr, "%s: overwrite completed.\n", ndctl_dimm_get_devname(dimm)); return rc; } static int __action_init(struct ndctl_dimm *dimm, enum ndctl_namespace_version version, int chk_only) { struct ndctl_cmd *cmd_read; int rc; cmd_read = ndctl_dimm_read_label_index(dimm); if (!cmd_read) return -ENXIO; /* * If the region goes active after this point, i.e. we're racing * another administrative action, the kernel will fail writes to * the label area. */ if (!chk_only && ndctl_dimm_is_active(dimm)) { fprintf(stderr, "%s: regions active, abort label write\n", ndctl_dimm_get_devname(dimm)); rc = -EBUSY; goto out; } rc = ndctl_dimm_validate_labels(dimm); if (chk_only) goto out; if (rc >= 0 && !param.force) { fprintf(stderr, "%s: error: labels already initialized\n", ndctl_dimm_get_devname(dimm)); rc = -EBUSY; goto out; } rc = ndctl_dimm_init_labels(dimm, version); if (rc < 0) goto out; /* * If the dimm is already disabled the kernel is not holding a cached * copy of the label space. */ if (!ndctl_dimm_is_enabled(dimm)) goto out; rc = ndctl_dimm_disable(dimm); if (rc) goto out; rc = ndctl_dimm_enable(dimm); out: ndctl_cmd_unref(cmd_read); return rc >= 0 ? 0 : rc; } static int action_init(struct ndctl_dimm *dimm, struct action_context *actx) { return __action_init(dimm, actx->labelversion, 0); } static int action_check(struct ndctl_dimm *dimm, struct action_context *actx) { return __action_init(dimm, 0, 1); } #define BASE_OPTIONS() \ OPT_STRING('b', "bus", ¶m.bus, "bus-id", \ " must be on a bus with an id/provider of "), \ OPT_BOOLEAN('v',"verbose", ¶m.verbose, "turn on debug") #define READ_OPTIONS() \ OPT_STRING('o', "output", ¶m.outfile, "output-file", \ "filename to write label area contents"), \ OPT_BOOLEAN('j', "json", ¶m.json, "parse label data into json"), \ OPT_BOOLEAN('u', "human", ¶m.human, "use human friendly number formats (implies --json)"), \ OPT_BOOLEAN('I', "index", ¶m.index, "limit read to the index block area") #define WRITE_OPTIONS() \ OPT_STRING('i', "input", ¶m.infile, "input-file", \ "filename to read label area data") #define UPDATE_OPTIONS() \ OPT_STRING('f', "firmware", ¶m.infile, "firmware-file", \ "firmware filename for update"), \ OPT_BOOLEAN('i', "force", ¶m.force, "ignore ARS / arm status, try to force update"), \ OPT_BOOLEAN_SET('A', "arm", ¶m.arm, ¶m.arm_set, \ "arm device for firmware activation (default)"), \ OPT_BOOLEAN_SET('D', "disarm", ¶m.disarm, ¶m.disarm_set, \ "disarm device for firmware activation") #define INIT_OPTIONS() \ OPT_BOOLEAN('f', "force", ¶m.force, \ "force initialization even if existing index-block present"), \ OPT_STRING('V', "label-version", ¶m.labelversion, "version-number", \ "namespace label specification version (default: 1.1)") #define KEY_OPTIONS() \ OPT_STRING('k', "key-handle", ¶m.kek, "key-handle", \ "master encryption key handle") #define SANITIZE_OPTIONS() \ OPT_BOOLEAN('c', "crypto-erase", ¶m.crypto_erase, \ "crypto erase a dimm"), \ OPT_BOOLEAN('o', "overwrite", ¶m.overwrite, \ "overwrite a dimm"), \ OPT_BOOLEAN('z', "zero-key", ¶m.zero_key, \ "pass in a zero key") #define MASTER_OPTIONS() \ OPT_BOOLEAN('m', "master-passphrase", ¶m.master_pass, \ "use master passphrase") #define LABEL_OPTIONS() \ OPT_UINTEGER('s', "size", ¶m.len, "number of label bytes to operate"), \ OPT_UINTEGER('O', "offset", ¶m.offset, \ "offset into the label area to start operation") static const struct option read_options[] = { BASE_OPTIONS(), LABEL_OPTIONS(), READ_OPTIONS(), OPT_END(), }; static const struct option write_options[] = { BASE_OPTIONS(), LABEL_OPTIONS(), WRITE_OPTIONS(), OPT_END(), }; static const struct option zero_options[] = { BASE_OPTIONS(), LABEL_OPTIONS(), OPT_END(), }; static const struct option update_options[] = { BASE_OPTIONS(), UPDATE_OPTIONS(), OPT_END(), }; static const struct option base_options[] = { BASE_OPTIONS(), OPT_END(), }; static const struct option init_options[] = { BASE_OPTIONS(), INIT_OPTIONS(), OPT_END(), }; static const struct option key_options[] = { BASE_OPTIONS(), KEY_OPTIONS(), MASTER_OPTIONS(), }; static const struct option sanitize_options[] = { BASE_OPTIONS(), SANITIZE_OPTIONS(), MASTER_OPTIONS(), OPT_END(), }; static const struct option remove_options[] = { BASE_OPTIONS(), MASTER_OPTIONS(), OPT_END(), }; static int dimm_action(int argc, const char **argv, struct ndctl_ctx *ctx, int (*action)(struct ndctl_dimm *dimm, struct action_context *actx), const struct option *options, const char *usage) { struct action_context actx = { 0 }; int i, rc = 0, count = 0, err = 0; struct ndctl_dimm *single = NULL; const char * const u[] = { usage, NULL }; unsigned long id; bool json = false; argc = parse_options(argc, argv, options, u, 0); if (argc == 0) usage_with_options(u, options); for (i = 0; i < argc; i++) { if (strcmp(argv[i], "all") == 0) { argv[0] = "all"; argc = 1; break; } if (sscanf(argv[i], "nmem%lu", &id) != 1) { fprintf(stderr, "'%s' is not a valid dimm name\n", argv[i]); err++; } } if (err == argc) { usage_with_options(u, options); return -EINVAL; } json = param.json || param.human || action == action_update; if (action == action_read && json && (param.len || param.offset)) { fprintf(stderr, "--size and --offset are incompatible with --json\n"); usage_with_options(u, options); return -EINVAL; } if (param.index && param.len) { fprintf(stderr, "pick either --size, or --index, not both\n"); usage_with_options(u, options); return -EINVAL; } if (json) { actx.jdimms = json_object_new_array(); if (!actx.jdimms) return -ENOMEM; } if (!param.outfile) actx.f_out = stdout; else { actx.f_out = fopen(param.outfile, "w+"); if (!actx.f_out) { fprintf(stderr, "failed to open: %s: (%s)\n", param.outfile, strerror(errno)); rc = -errno; goto out; } } if (param.arm_set && param.disarm_set) { fprintf(stderr, "set either --arm, or --disarm, not both\n"); usage_with_options(u, options); } if (param.disarm_set && !param.disarm) { fprintf(stderr, "--no-disarm syntax not supported\n"); usage_with_options(u, options); return -EINVAL; } if (!param.infile) { /* * Update needs an infile unless we are only being * called to toggle the arm state. Other actions either * do no need an input file, or are prepared for stdin. */ if (action == action_update) { if (!param.arm_set && !param.disarm_set) { fprintf(stderr, "require --arm, or --disarm\n"); usage_with_options(u, options); return -EINVAL; } if (param.arm_set && !param.arm) { fprintf(stderr, "--no-arm syntax not supported\n"); usage_with_options(u, options); return -EINVAL; } } actx.f_in = stdin; } else { actx.f_in = fopen(param.infile, "r"); if (!actx.f_in) { fprintf(stderr, "failed to open: %s: (%s)\n", param.infile, strerror(errno)); rc = -errno; goto out_close_fout; } } if (param.verbose) ndctl_set_log_priority(ctx, LOG_DEBUG); if (strcmp(param.labelversion, "1.1") == 0) actx.labelversion = NDCTL_NS_VERSION_1_1; else if (strcmp(param.labelversion, "v1.1") == 0) actx.labelversion = NDCTL_NS_VERSION_1_1; else if (strcmp(param.labelversion, "1.2") == 0) actx.labelversion = NDCTL_NS_VERSION_1_2; else if (strcmp(param.labelversion, "v1.2") == 0) actx.labelversion = NDCTL_NS_VERSION_1_2; else { fprintf(stderr, "'%s' is not a valid label version\n", param.labelversion); rc = -EINVAL; goto out_close_fin_fout; } rc = 0; err = 0; count = 0; for (i = 0; i < argc; i++) { struct ndctl_dimm *dimm; struct ndctl_bus *bus; if (sscanf(argv[i], "nmem%lu", &id) != 1 && strcmp(argv[i], "all") != 0) continue; ndctl_bus_foreach(ctx, bus) { if (!util_bus_filter(bus, param.bus)) continue; ndctl_dimm_foreach(bus, dimm) { if (!util_dimm_filter(dimm, argv[i])) continue; if (action == action_write) { single = dimm; rc = 0; } else rc = action(dimm, &actx); if (rc == 0) count++; else if (rc && !err) err = rc; } } } rc = err; if (action == action_write) { if (count > 1) { error("write-labels only supports writing a single dimm\n"); usage_with_options(u, options); return -EINVAL; } else if (single) rc = action(single, &actx); } if (actx.jdimms && json_object_array_length(actx.jdimms) > 0) { unsigned long flags = 0; if (actx.f_out == stdout && isatty(1)) flags |= UTIL_JSON_HUMAN; util_display_json_array(actx.f_out, actx.jdimms, flags); } out_close_fin_fout: if (actx.f_in != stdin) fclose(actx.f_in); out_close_fout: if (actx.f_out != stdout) fclose(actx.f_out); out: /* * count if some actions succeeded, 0 if none were attempted, * negative error code otherwise. */ if (count > 0) return count; return rc; } int cmd_write_labels(int argc, const char **argv, struct ndctl_ctx *ctx) { int count = dimm_action(argc, argv, ctx, action_write, write_options, "ndctl write-labels [-i ]"); fprintf(stderr, "wrote %d nmem%s\n", count >= 0 ? count : 0, count > 1 ? "s" : ""); return count >= 0 ? 0 : EXIT_FAILURE; } int cmd_read_labels(int argc, const char **argv, struct ndctl_ctx *ctx) { int count = dimm_action(argc, argv, ctx, action_read, read_options, "ndctl read-labels [..] [-o ]"); fprintf(stderr, "read %d nmem%s\n", count >= 0 ? count : 0, count > 1 ? "s" : ""); return count >= 0 ? 0 : EXIT_FAILURE; } int cmd_zero_labels(int argc, const char **argv, struct ndctl_ctx *ctx) { int count = dimm_action(argc, argv, ctx, action_zero, zero_options, "ndctl zero-labels [..] []"); fprintf(stderr, "zeroed %d nmem%s\n", count >= 0 ? count : 0, count > 1 ? "s" : ""); return count >= 0 ? 0 : EXIT_FAILURE; } int cmd_init_labels(int argc, const char **argv, struct ndctl_ctx *ctx) { int count = dimm_action(argc, argv, ctx, action_init, init_options, "ndctl init-labels [..] []"); fprintf(stderr, "initialized %d nmem%s\n", count >= 0 ? count : 0, count > 1 ? "s" : ""); return count >= 0 ? 0 : EXIT_FAILURE; } int cmd_check_labels(int argc, const char **argv, struct ndctl_ctx *ctx) { int count = dimm_action(argc, argv, ctx, action_check, base_options, "ndctl check-labels [..] []"); fprintf(stderr, "successfully verified %d nmem label%s\n", count >= 0 ? count : 0, count > 1 ? "s" : ""); return count >= 0 ? 0 : EXIT_FAILURE; } int cmd_disable_dimm(int argc, const char **argv, struct ndctl_ctx *ctx) { int count = dimm_action(argc, argv, ctx, action_disable, base_options, "ndctl disable-dimm [..] []"); fprintf(stderr, "disabled %d nmem%s\n", count >= 0 ? count : 0, count > 1 ? "s" : ""); return count >= 0 ? 0 : EXIT_FAILURE; } int cmd_enable_dimm(int argc, const char **argv, struct ndctl_ctx *ctx) { int count = dimm_action(argc, argv, ctx, action_enable, base_options, "ndctl enable-dimm [..] []"); fprintf(stderr, "enabled %d nmem%s\n", count >= 0 ? count : 0, count > 1 ? "s" : ""); return count >= 0 ? 0 : EXIT_FAILURE; } int cmd_update_firmware(int argc, const char **argv, struct ndctl_ctx *ctx) { int count; cmd_name = "update firmware"; count = dimm_action(argc, argv, ctx, action_update, update_options, "ndctl update-firmware [..] []"); fprintf(stderr, "updated %d nmem%s.\n", count >= 0 ? count : 0, count > 1 ? "s" : ""); return count >= 0 ? 0 : EXIT_FAILURE; } int cmd_update_passphrase(int argc, const char **argv, struct ndctl_ctx *ctx) { int count = dimm_action(argc, argv, ctx, action_update_passphrase, key_options, "ndctl update-passphrase [..] []"); fprintf(stderr, "passphrase updated for %d nmem%s.\n", count >= 0 ? count : 0, count > 1 ? "s" : ""); return count >= 0 ? 0 : EXIT_FAILURE; } int cmd_setup_passphrase(int argc, const char **argv, struct ndctl_ctx *ctx) { int count = dimm_action(argc, argv, ctx, action_setup_passphrase, key_options, "ndctl setup-passphrase [..] []"); fprintf(stderr, "passphrase enabled for %d nmem%s.\n", count >= 0 ? count : 0, count > 1 ? "s" : ""); return count >= 0 ? 0 : EXIT_FAILURE; } int cmd_remove_passphrase(int argc, const char **argv, void *ctx) { int count = dimm_action( argc, argv, ctx, action_remove_passphrase, remove_options, "ndctl remove-passphrase [..] []"); fprintf(stderr, "passphrase removed for %d nmem%s.\n", count >= 0 ? count : 0, count > 1 ? "s" : ""); return count >= 0 ? 0 : EXIT_FAILURE; } int cmd_freeze_security(int argc, const char **argv, void *ctx) { int count = dimm_action(argc, argv, ctx, action_security_freeze, base_options, "ndctl freeze-security [..] []"); fprintf(stderr, "security froze %d nmem%s.\n", count >= 0 ? count : 0, count > 1 ? "s" : ""); return count >= 0 ? 0 : EXIT_FAILURE; } int cmd_sanitize_dimm(int argc, const char **argv, void *ctx) { int count = dimm_action(argc, argv, ctx, action_sanitize_dimm, sanitize_options, "ndctl sanitize-dimm [..] []"); if (param.overwrite) fprintf(stderr, "overwrite issued for %d nmem%s.\n", count >= 0 ? count : 0, count > 1 ? "s" : ""); else fprintf(stderr, "sanitized %d nmem%s.\n", count >= 0 ? count : 0, count > 1 ? "s" : ""); return count >= 0 ? 0 : EXIT_FAILURE; } int cmd_wait_overwrite(int argc, const char **argv, void *ctx) { int count = dimm_action(argc, argv, ctx, action_wait_overwrite, base_options, "ndctl wait-overwrite [..] []"); return count >= 0 ? 0 : EXIT_FAILURE; } ndctl-81/ndctl/filter.c000066400000000000000000000246501476737544500152020ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2015-2020 Intel Corporation. All rights reserved. #include #include #include #include #include #include #include #include #include #include #include "filter.h" struct ndctl_bus *util_bus_filter(struct ndctl_bus *bus, const char *__ident) { char *end = NULL, *ident, *save; unsigned long bus_id, id; const char *provider, *devname, *name; if (!__ident) return bus; ident = strdup(__ident); if (!ident) return NULL; for (name = strtok_r(ident, " ", &save); name; name = strtok_r(NULL, " ", &save)) { if (strcmp(name, "all") == 0) break; bus_id = strtoul(ident, &end, 0); if (end == ident || end[0]) bus_id = ULONG_MAX; provider = ndctl_bus_get_provider(bus); devname = ndctl_bus_get_devname(bus); id = ndctl_bus_get_id(bus); if (bus_id < ULONG_MAX && bus_id == id) break; if (bus_id == ULONG_MAX && (strcmp(provider, name) == 0 || strcmp(devname, name) == 0)) break; } free(ident); if (name) return bus; return NULL; } struct ndctl_region *util_region_filter(struct ndctl_region *region, const char *__ident) { char *end = NULL, *ident, *save; const char *name, *region_name; unsigned long region_id, id; if (!__ident) return region; ident = strdup(__ident); if (!ident) return NULL; for (name = strtok_r(ident, " ", &save); name; name = strtok_r(NULL, " ", &save)) { if (strcmp(name, "all") == 0) break; region_id = strtoul(ident, &end, 0); if (end == ident || end[0]) region_id = ULONG_MAX; region_name = ndctl_region_get_devname(region); id = ndctl_region_get_id(region); if (region_id < ULONG_MAX && region_id == id) break; if (region_id == ULONG_MAX && strcmp(region_name, name) == 0) break; } free(ident); if (name) return region; return NULL; } struct ndctl_namespace *util_namespace_filter(struct ndctl_namespace *ndns, const char *__ident) { struct ndctl_region *region = ndctl_namespace_get_region(ndns); unsigned long region_id, ndns_id; const char *name; char *ident, *save; if (!__ident) return ndns; ident = strdup(__ident); if (!ident) return NULL; for (name = strtok_r(ident, " ", &save); name; name = strtok_r(NULL, " ", &save)) { if (strcmp(name, "all") == 0) break; if (strcmp(name, ndctl_namespace_get_devname(ndns)) == 0) break; if (sscanf(name, "%ld.%ld", ®ion_id, &ndns_id) == 2 && ndctl_region_get_id(region) == region_id && ndctl_namespace_get_id(ndns) == ndns_id) break; } free(ident); if (name) return ndns; return NULL; } struct ndctl_dimm *util_dimm_filter(struct ndctl_dimm *dimm, const char *__ident) { char *end = NULL, *ident, *save; const char *name, *dimm_name; unsigned long dimm_id, id; if (!__ident) return dimm; ident = strdup(__ident); if (!ident) return NULL; for (name = strtok_r(ident, " ", &save); name; name = strtok_r(NULL, " ", &save)) { if (strcmp(name, "all") == 0) break; dimm_id = strtoul(ident, &end, 0); if (end == ident || end[0]) dimm_id = ULONG_MAX; dimm_name = ndctl_dimm_get_devname(dimm); id = ndctl_dimm_get_id(dimm); if (dimm_id < ULONG_MAX && dimm_id == id) break; if (dimm_id == ULONG_MAX && strcmp(dimm_name, name) == 0) break; } free(ident); if (name) return dimm; return NULL; } struct ndctl_bus *util_bus_filter_by_dimm(struct ndctl_bus *bus, const char *ident) { struct ndctl_dimm *dimm; if (!ident || strcmp(ident, "all") == 0) return bus; ndctl_dimm_foreach(bus, dimm) if (util_dimm_filter(dimm, ident)) return bus; return NULL; } struct ndctl_bus *util_bus_filter_by_region(struct ndctl_bus *bus, const char *ident) { struct ndctl_region *region; if (!ident || strcmp(ident, "all") == 0) return bus; ndctl_region_foreach(bus, region) if (util_region_filter(region, ident)) return bus; return NULL; } struct ndctl_bus *util_bus_filter_by_namespace(struct ndctl_bus *bus, const char *ident) { struct ndctl_region *region; struct ndctl_namespace *ndns; if (!ident || strcmp(ident, "all") == 0) return bus; ndctl_region_foreach(bus, region) ndctl_namespace_foreach(region, ndns) if (util_namespace_filter(ndns, ident)) return bus; return NULL; } struct ndctl_region *util_region_filter_by_dimm(struct ndctl_region *region, const char *ident) { struct ndctl_dimm *dimm; if (!ident || strcmp(ident, "all") == 0) return region; ndctl_dimm_foreach_in_region(region, dimm) if (util_dimm_filter(dimm, ident)) return region; return NULL; } struct ndctl_dimm *util_dimm_filter_by_region(struct ndctl_dimm *dimm, const char *ident) { struct ndctl_bus *bus = ndctl_dimm_get_bus(dimm); struct ndctl_region *region; struct ndctl_dimm *check; if (!ident || strcmp(ident, "all") == 0) return dimm; ndctl_region_foreach(bus, region) { if (!util_region_filter(region, ident)) continue; ndctl_dimm_foreach_in_region(region, check) if (check == dimm) return dimm; } return NULL; } struct ndctl_dimm *util_dimm_filter_by_namespace(struct ndctl_dimm *dimm, const char *ident) { struct ndctl_bus *bus = ndctl_dimm_get_bus(dimm); struct ndctl_namespace *ndns; struct ndctl_region *region; struct ndctl_dimm *check; if (!ident || strcmp(ident, "all") == 0) return dimm; ndctl_region_foreach(bus, region) { ndctl_namespace_foreach(region, ndns) { if (!util_namespace_filter(ndns, ident)) continue; ndctl_dimm_foreach_in_region(region, check) if (check == dimm) return dimm; } } return NULL; } struct ndctl_dimm *util_dimm_filter_by_numa_node(struct ndctl_dimm *dimm, int numa_node) { struct ndctl_bus *bus = ndctl_dimm_get_bus(dimm); struct ndctl_region *region; struct ndctl_dimm *check; if (numa_node == NUMA_NO_NODE) return dimm; ndctl_region_foreach(bus, region) ndctl_dimm_foreach_in_region(region, check) if (check == dimm && ndctl_region_get_numa_node(region) == numa_node) return dimm; return NULL; } struct ndctl_region *util_region_filter_by_namespace(struct ndctl_region *region, const char *ident) { struct ndctl_namespace *ndns; if (!ident || strcmp(ident, "all") == 0) return region; ndctl_namespace_foreach(region, ndns) if (util_namespace_filter(ndns, ident)) return region; return NULL; } enum ndctl_namespace_mode util_nsmode(const char *mode) { if (!mode) return NDCTL_NS_MODE_UNKNOWN; if (strcasecmp(mode, "memory") == 0) return NDCTL_NS_MODE_FSDAX; if (strcasecmp(mode, "fsdax") == 0) return NDCTL_NS_MODE_FSDAX; if (strcasecmp(mode, "sector") == 0) return NDCTL_NS_MODE_SECTOR; if (strcasecmp(mode, "safe") == 0) return NDCTL_NS_MODE_SECTOR; if (strcasecmp(mode, "dax") == 0) return NDCTL_NS_MODE_DEVDAX; if (strcasecmp(mode, "devdax") == 0) return NDCTL_NS_MODE_DEVDAX; if (strcasecmp(mode, "raw") == 0) return NDCTL_NS_MODE_RAW; return NDCTL_NS_MODE_UNKNOWN; } const char *util_nsmode_name(enum ndctl_namespace_mode mode) { static const char * const modes[] = { [NDCTL_NS_MODE_FSDAX] = "fsdax", [NDCTL_NS_MODE_DEVDAX] = "devdax", [NDCTL_NS_MODE_RAW] = "raw", [NDCTL_NS_MODE_SECTOR] = "sector", [NDCTL_NS_MODE_UNKNOWN] = "unknown", }; return modes[mode]; } int ndctl_filter_walk(struct ndctl_ctx *ctx, struct ndctl_filter_ctx *fctx, struct ndctl_filter_params *param) { struct ndctl_bus *bus; unsigned int type = 0; int numa_node = NUMA_NO_NODE; char *end = NULL; if (param->type && (strcmp(param->type, "pmem") != 0 && strcmp(param->type, "blk") != 0)) { error("unknown type \"%s\" must be \"pmem\" or \"blk\"\n", param->type); return -EINVAL; } if (param->type) { if (strcmp(param->type, "pmem") == 0) type = ND_DEVICE_REGION_PMEM; else type = ND_DEVICE_REGION_BLK; } if (param->mode && util_nsmode(param->mode) == NDCTL_NS_MODE_UNKNOWN) { error("invalid mode: '%s'\n", param->mode); return -EINVAL; } if (param->numa_node && strcmp(param->numa_node, "all") != 0) { struct stat st; if (stat("/sys/devices/system/node", &st) != 0) { error("This system does not support NUMA"); return -EINVAL; } numa_node = strtol(param->numa_node, &end, 0); if (end == param->numa_node || end[0]) { error("invalid numa_node: '%s'\n", param->numa_node); return -EINVAL; } } ndctl_bus_foreach(ctx, bus) { struct ndctl_region *region; struct ndctl_dimm *dimm; if (!util_bus_filter(bus, param->bus) || !util_bus_filter_by_dimm(bus, param->dimm) || !util_bus_filter_by_region(bus, param->region) || !util_bus_filter_by_namespace(bus, param->namespace)) continue; if (!fctx->filter_bus(bus, fctx)) continue; ndctl_dimm_foreach(bus, dimm) { if (!fctx->filter_dimm) break; if (!util_dimm_filter(dimm, param->dimm) || !util_dimm_filter_by_region(dimm, param->region) || !util_dimm_filter_by_namespace(dimm, param->namespace) || !util_dimm_filter_by_numa_node(dimm, numa_node)) continue; fctx->filter_dimm(dimm, fctx); } ndctl_region_foreach(bus, region) { struct ndctl_namespace *ndns; if (!util_region_filter(region, param->region) || !util_region_filter_by_dimm(region, param->dimm) || !util_region_filter_by_namespace(region, param->namespace)) continue; /* * if numa_node attribute is not available for regions * (which is true for pre 5.4 kernels), don't skip the * region if namespace is also requested, let the * namespace filter handle the NUMA node filtering. */ if (numa_node != NUMA_NO_NODE && !ndctl_region_has_numa(region) && !fctx->filter_namespace) { fprintf(stderr, "This kernel does not provide NUMA node information per-region\n"); continue; } if (ndctl_region_has_numa(region) && numa_node != NUMA_NO_NODE && ndctl_region_get_numa_node(region) != numa_node) continue; if (type && ndctl_region_get_type(region) != type) continue; if (!fctx->filter_region(region, fctx)) continue; ndctl_namespace_foreach(region, ndns) { enum ndctl_namespace_mode mode; if (!fctx->filter_namespace) break; if (!util_namespace_filter(ndns, param->namespace)) continue; mode = ndctl_namespace_get_mode(ndns); if (param->mode && util_nsmode(param->mode) != mode) continue; if (numa_node != NUMA_NO_NODE && ndctl_namespace_get_numa_node(ndns) != numa_node) continue; fctx->filter_namespace(ndns, fctx); } } } return 0; } ndctl-81/ndctl/filter.h000066400000000000000000000056021476737544500152030ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (C) 2015-2020 Intel Corporation. All rights reserved. */ #ifndef _NDCTL_UTIL_FILTER_H_ #define _NDCTL_UTIL_FILTER_H_ #include #include struct ndctl_bus *util_bus_filter(struct ndctl_bus *bus, const char *ident); struct ndctl_region *util_region_filter(struct ndctl_region *region, const char *ident); struct ndctl_namespace *util_namespace_filter(struct ndctl_namespace *ndns, const char *ident); struct ndctl_dimm *util_dimm_filter(struct ndctl_dimm *dimm, const char *ident); struct ndctl_bus *util_bus_filter_by_dimm(struct ndctl_bus *bus, const char *ident); struct ndctl_bus *util_bus_filter_by_region(struct ndctl_bus *bus, const char *ident); struct ndctl_bus *util_bus_filter_by_namespace(struct ndctl_bus *bus, const char *ident); struct ndctl_region *util_region_filter_by_dimm(struct ndctl_region *region, const char *ident); struct ndctl_dimm *util_dimm_filter_by_region(struct ndctl_dimm *dimm, const char *ident); struct ndctl_dimm *util_dimm_filter_by_namespace(struct ndctl_dimm *dimm, const char *ident); struct ndctl_region *util_region_filter_by_namespace(struct ndctl_region *region, const char *ident); enum ndctl_namespace_mode util_nsmode(const char *mode); const char *util_nsmode_name(enum ndctl_namespace_mode mode); struct json_object; /* json object hierarchy for the ndctl_filter_walk() performed by cmd_list() */ struct list_filter_arg { struct json_object *jnamespaces; struct json_object *jregions; struct json_object *jdimms; struct json_object *jbuses; struct json_object *jregion; struct json_object *jbus; unsigned long flags; }; struct monitor_filter_arg { struct list_head dimms; int maxfd_dimm; int num_dimm; unsigned long flags; }; /* * struct ndctl_filter_ctx - control and callbacks for ndctl_filter_walk() * ->filter_bus() and ->filter_region() return bool because the * child-object filter routines can not be called if the parent context * is not established. ->filter_dimm() and ->filter_namespace() are leaf * objects, so no child dependencies to check. */ struct ndctl_filter_ctx { bool (*filter_bus)(struct ndctl_bus *bus, struct ndctl_filter_ctx *ctx); void (*filter_dimm)(struct ndctl_dimm *dimm, struct ndctl_filter_ctx *ctx); bool (*filter_region)(struct ndctl_region *region, struct ndctl_filter_ctx *ctx); void (*filter_namespace)(struct ndctl_namespace *ndns, struct ndctl_filter_ctx *ctx); union { void *arg; struct list_filter_arg *list; struct monitor_filter_arg *monitor; }; }; struct ndctl_filter_params { const char *bus; const char *region; const char *type; const char *dimm; const char *mode; const char *namespace; const char *numa_node; }; struct ndctl_ctx; int ndctl_filter_walk(struct ndctl_ctx *ctx, struct ndctl_filter_ctx *fctx, struct ndctl_filter_params *param); #endif /* _NDCTL_UTIL_FILTER_H_ */ ndctl-81/ndctl/firmware-update.h000066400000000000000000000023201476737544500170040ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (C) 2018-2020 Intel Corporation. All rights reserved. */ #ifndef _FIRMWARE_UPDATE_H_ #define _FIRMWARE_UPDATE_H_ #define ND_CMD_STATUS_SUCCESS 0 #define ND_CMD_STATUS_NOTSUPP 1 #define ND_CMD_STATUS_NOTEXIST 2 #define ND_CMD_STATUS_INVALPARM 3 #define ND_CMD_STATUS_HWERR 4 #define ND_CMD_STATUS_RETRY 5 #define ND_CMD_STATUS_UNKNOWN 6 #define ND_CMD_STATUS_EXTEND 7 #define ND_CMD_STATUS_NORES 8 #define ND_CMD_STATUS_NOTREADY 9 /* extended status through ND_CMD_STATUS_EXTEND */ #define ND_CMD_STATUS_START_BUSY 0x10000 #define ND_CMD_STATUS_SEND_CTXINVAL 0x10000 #define ND_CMD_STATUS_FIN_CTXINVAL 0x10000 #define ND_CMD_STATUS_FIN_DONE 0x20000 #define ND_CMD_STATUS_FIN_BAD 0x30000 #define ND_CMD_STATUS_FIN_ABORTED 0x40000 #define ND_CMD_STATUS_FQ_CTXINVAL 0x10000 #define ND_CMD_STATUS_FQ_BUSY 0x20000 #define ND_CMD_STATUS_FQ_BAD 0x30000 #define ND_CMD_STATUS_FQ_ORDER 0x40000 struct fw_info { uint32_t store_size; uint32_t update_size; uint32_t query_interval; uint32_t max_query; uint64_t run_version; uint32_t context; }; struct update_context { size_t fw_size; struct fw_info dimm_fw; struct ndctl_cmd *start; struct json_object *jdimms; }; #endif ndctl-81/ndctl/inject-error.c000066400000000000000000000213661476737544500163210ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2015-2020 Intel Corporation. All rights reserved. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "filter.h" #include "json.h" static bool verbose; static struct parameters { const char *bus; const char *region; const char *namespace; const char *block; const char *count; bool clear; bool status; bool no_notify; bool saturate; bool human; } param; static struct inject_ctx { u64 block; u64 count; unsigned int op_mask; unsigned long json_flags; unsigned int inject_flags; } ictx; #define BASE_OPTIONS() \ OPT_STRING('b', "bus", ¶m.bus, "bus-id", \ "limit namespace to a bus with an id or provider of "), \ OPT_STRING('r', "region", ¶m.region, "region-id", \ "limit namespace to a region with an id or name of "), \ OPT_BOOLEAN('v', "verbose", &verbose, "emit extra debug messages to stderr") #define INJECT_OPTIONS() \ OPT_STRING('B', "block", ¶m.block, "namespace block offset (512B)", \ "specify the block at which to (un)inject the error"), \ OPT_STRING('n', "count", ¶m.count, "count", \ "specify the number of blocks of errors to (un)inject"), \ OPT_BOOLEAN('d', "uninject", ¶m.clear, \ "un-inject a previously injected error"), \ OPT_BOOLEAN('t', "status", ¶m.status, "get error injection status"), \ OPT_BOOLEAN('N', "no-notify", ¶m.no_notify, "firmware should not notify OS"), \ OPT_BOOLEAN('S', "saturate", ¶m.saturate, \ "inject full sector, not just 'ars_unit' bytes"), \ OPT_BOOLEAN('u', "human", ¶m.human, "use human friendly number formats ") static const struct option inject_options[] = { BASE_OPTIONS(), INJECT_OPTIONS(), OPT_END(), }; enum { OP_INJECT = 0, OP_CLEAR, OP_STATUS, }; static int inject_init(void) { if (!param.clear && !param.status) { ictx.op_mask |= 1 << OP_INJECT; ictx.inject_flags |= (1 << NDCTL_NS_INJECT_NOTIFY); if (param.no_notify) ictx.inject_flags &= ~(1 << NDCTL_NS_INJECT_NOTIFY); } if (param.clear) { if (param.status) { error("status is invalid with inject or uninject\n"); return -EINVAL; } ictx.op_mask |= 1 << OP_CLEAR; } if (param.status) { if (param.block || param.count || param.saturate) { error("status is invalid with inject or uninject\n"); return -EINVAL; } ictx.op_mask |= 1 << OP_STATUS; } if (ictx.op_mask == 0) { error("Unable to determine operation\n"); return -EINVAL; } ictx.op_mask &= ( (1 << OP_INJECT) | (1 << OP_CLEAR) | (1 << OP_STATUS)); if (param.block) { ictx.block = parse_size64(param.block); if (ictx.block == ULLONG_MAX) { error("Invalid block: %s\n", param.block); return -EINVAL; } } if (param.count) { ictx.count = parse_size64(param.count); if (ictx.count == ULLONG_MAX) { error("Invalid count: %s\n", param.count); return -EINVAL; } } /* For inject or clear, an block and count are required */ if (ictx.op_mask & ((1 << OP_INJECT) | (1 << OP_CLEAR))) { if (!param.block || !param.count) { error("block and count required for inject/uninject\n"); return -EINVAL; } } if (param.human) ictx.json_flags |= UTIL_JSON_HUMAN; if (param.saturate) ictx.inject_flags |= 1 << NDCTL_NS_INJECT_SATURATE; return 0; } static int ns_errors_to_json(struct ndctl_namespace *ndns, unsigned int start_count) { unsigned long json_flags = ictx.json_flags | UTIL_JSON_MEDIA_ERRORS; struct ndctl_bus *bus = ndctl_namespace_get_bus(ndns); struct json_object *jndns; unsigned int count; int rc, tmo = 30; /* only wait for scrubs for the inject and notify case */ if ((ictx.op_mask & (1 << OP_INJECT)) && (ictx.inject_flags & (1 << NDCTL_NS_INJECT_NOTIFY))) { do { /* wait for a scrub to start */ count = ndctl_bus_get_scrub_count(bus); if (count == UINT_MAX) { fprintf(stderr, "Unable to get scrub count\n"); return -ENXIO; } sleep(1); } while (count <= start_count && --tmo > 0); rc = ndctl_bus_wait_for_scrub_completion(bus); if (rc) { fprintf(stderr, "Error waiting for scrub\n"); return rc; } } jndns = util_namespace_to_json(ndns, json_flags); if (jndns) printf("%s\n", json_object_to_json_string_ext(jndns, JSON_C_TO_STRING_PRETTY)); return 0; } static int inject_error(struct ndctl_namespace *ndns, u64 offset, u64 length, unsigned int flags) { struct ndctl_bus *bus = ndctl_namespace_get_bus(ndns); unsigned int scrub_count; int rc; scrub_count = ndctl_bus_get_scrub_count(bus); if (scrub_count == UINT_MAX) { fprintf(stderr, "Unable to get scrub count\n"); return -ENXIO; } rc = ndctl_namespace_inject_error2(ndns, offset, length, flags); if (rc) { fprintf(stderr, "Unable to inject error: %s (%d)\n", strerror(abs(rc)), rc); return rc; } return ns_errors_to_json(ndns, scrub_count); } static int uninject_error(struct ndctl_namespace *ndns, u64 offset, u64 length, unsigned int flags) { int rc; rc = ndctl_namespace_uninject_error2(ndns, offset, length, flags); if (rc) { fprintf(stderr, "Unable to uninject error: %s (%d)\n", strerror(abs(rc)), rc); return rc; } printf("Warning: Un-injecting previously injected errors here will\n"); printf("not cause the kernel to 'forget' its badblock entries. Those\n"); printf("have to be cleared through the normal process of writing\n"); printf("the affected blocks\n\n"); return ns_errors_to_json(ndns, 0); } static int injection_status(struct ndctl_namespace *ndns) { unsigned long long block, count, bbs = 0; struct json_object *jbbs, *jbb, *jobj; struct ndctl_bb *bb; int rc; rc = ndctl_namespace_injection_status(ndns); if (rc) { fprintf(stderr, "Unable to get injection status: %s (%d)\n", strerror(abs(rc)), rc); return rc; } jobj = json_object_new_object(); if (!jobj) return -ENOMEM; jbbs = json_object_new_array(); if (!jbbs) { json_object_put(jobj); return -ENOMEM; } ndctl_namespace_bb_foreach(ndns, bb) { block = ndctl_bb_get_block(bb); count = ndctl_bb_get_count(bb); jbb = util_badblock_rec_to_json(block, count, ictx.json_flags); if (!jbb) break; json_object_array_add(jbbs, jbb); bbs++; } if (bbs) { json_object_object_add(jobj, "badblocks", jbbs); printf("%s\n", json_object_to_json_string_ext(jobj, JSON_C_TO_STRING_PRETTY)); } json_object_put(jobj); return rc; } static int err_inject_ns(struct ndctl_namespace *ndns) { unsigned int op_mask; int rc; op_mask = ictx.op_mask; while (op_mask) { if (op_mask & (1 << OP_INJECT)) { rc = inject_error(ndns, ictx.block, ictx.count, ictx.inject_flags); if (rc) return rc; op_mask &= ~(1 << OP_INJECT); } if (op_mask & (1 << OP_CLEAR)) { rc = uninject_error(ndns, ictx.block, ictx.count, ictx.inject_flags); if (rc) return rc; op_mask &= ~(1 << OP_CLEAR); } if (op_mask & (1 << OP_STATUS)) { rc = injection_status(ndns); if (rc) return rc; op_mask &= ~(1 << OP_STATUS); } } return rc; } static int do_inject(const char *namespace, struct ndctl_ctx *ctx) { struct ndctl_namespace *ndns; struct ndctl_region *region; const char *ndns_name; struct ndctl_bus *bus; int rc = -ENXIO; if (namespace == NULL) return rc; if (verbose) ndctl_set_log_priority(ctx, LOG_DEBUG); ndctl_bus_foreach(ctx, bus) { if (!util_bus_filter(bus, param.bus)) continue; ndctl_region_foreach(bus, region) { if (!util_region_filter(region, param.region)) continue; ndctl_namespace_foreach(region, ndns) { ndns_name = ndctl_namespace_get_devname(ndns); if (strcmp(namespace, ndns_name) != 0) continue; if (!ndctl_bus_has_error_injection(bus)) { fprintf(stderr, "%s: error injection not supported\n", ndns_name); return -EOPNOTSUPP; } return err_inject_ns(ndns); } } } error("%s: no such namespace\n", namespace); return rc; } int cmd_inject_error(int argc, const char **argv, struct ndctl_ctx *ctx) { const char * const u[] = { "ndctl inject-error []", NULL }; int i, rc; argc = parse_options(argc, argv, inject_options, u, 0); rc = inject_init(); if (rc) return rc; if (argc == 0) error("specify a namespace to inject error to\n"); for (i = 1; i < argc; i++) error("unknown extra parameter \"%s\"\n", argv[i]); if (argc == 0 || argc > 1) { usage_with_options(u, inject_options); return -ENODEV; /* we won't return from usage_with_options() */ } return do_inject(argv[0], ctx); } ndctl-81/ndctl/inject-smart.c000066400000000000000000000342111476737544500163070ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) 2018-2020 Intel Corporation. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "filter.h" #include "json.h" static struct parameters { const char *bus; const char *dimm; bool verbose; bool human; const char *media_temperature; const char *ctrl_temperature; const char *spares; const char *media_temperature_threshold; const char *ctrl_temperature_threshold; const char *spares_threshold; const char *media_temperature_alarm; const char *ctrl_temperature_alarm; const char *spares_alarm; bool fatal; bool unsafe_shutdown; bool media_temperature_uninject; bool ctrl_temperature_uninject; bool spares_uninject; bool fatal_uninject; bool unsafe_shutdown_uninject; bool uninject_all; } param; static struct smart_ctx { bool alarms_present; bool err_continue; unsigned long op_mask; unsigned long flags; unsigned int media_temperature; unsigned int ctrl_temperature; unsigned long spares; unsigned int media_temperature_threshold; unsigned int ctrl_temperature_threshold; unsigned long spares_threshold; unsigned int media_temperature_alarm; unsigned int ctrl_temperature_alarm; unsigned long spares_alarm; } sctx; #define SMART_OPTIONS() \ OPT_STRING('b', "bus", ¶m.bus, "bus-id", \ "limit dimm to a bus with an id or provider of "), \ OPT_BOOLEAN('v', "verbose", ¶m.verbose, "emit extra debug messages to stderr"), \ OPT_BOOLEAN('u', "human", ¶m.human, "use human friendly number formats"), \ OPT_STRING('m', "media-temperature", ¶m.media_temperature, \ "smart media temperature attribute", \ "inject a value for smart media temperature"), \ OPT_STRING('M', "media-temperature-threshold", \ ¶m.media_temperature_threshold, \ "set smart media temperature threshold", \ "set threshold value for smart media temperature"), \ OPT_STRING('\0', "media-temperature-alarm", ¶m.media_temperature_alarm, \ "smart media temperature alarm", \ "enable or disable the smart media temperature alarm"), \ OPT_BOOLEAN('\0', "media-temperature-uninject", \ ¶m.media_temperature_uninject, "uninject media temperature"), \ OPT_STRING('c', "ctrl-temperature", ¶m.ctrl_temperature, \ "smart controller temperature attribute", \ "inject a value for smart controller temperature"), \ OPT_STRING('C', "ctrl-temperature-threshold", \ ¶m.ctrl_temperature_threshold, \ "set smart controller temperature threshold", \ "set threshold value for smart controller temperature"), \ OPT_STRING('\0', "ctrl-temperature-alarm", ¶m.ctrl_temperature_alarm, \ "smart controller temperature alarm", \ "enable or disable the smart controller temperature alarm"), \ OPT_BOOLEAN('\0', "ctrl-temperature-uninject", \ ¶m.ctrl_temperature_uninject, "uninject controller temperature"), \ OPT_STRING('s', "spares", ¶m.spares, \ "smart spares attribute", \ "inject a value for smart spares"), \ OPT_STRING('S', "spares-threshold", ¶m.spares_threshold, \ "set smart spares threshold", \ "set a threshold value for smart spares"), \ OPT_STRING('\0', "spares-alarm", ¶m.spares_alarm, \ "smart spares alarm", \ "enable or disable the smart spares alarm"), \ OPT_BOOLEAN('\0', "spares-uninject", \ ¶m.spares_uninject, "uninject spare percentage"), \ OPT_BOOLEAN('f', "fatal", ¶m.fatal, "inject fatal smart health status"), \ OPT_BOOLEAN('F', "fatal-uninject", \ ¶m.fatal_uninject, "uninject fatal health condition"), \ OPT_BOOLEAN('U', "unsafe-shutdown", ¶m.unsafe_shutdown, \ "inject smart unsafe shutdown status"), \ OPT_BOOLEAN('\0', "unsafe-shutdown-uninject", \ ¶m.unsafe_shutdown_uninject, "uninject unsafe shutdown status"), \ OPT_BOOLEAN('N', "uninject-all", \ ¶m.uninject_all, "uninject all possible fields") static const struct option smart_opts[] = { SMART_OPTIONS(), OPT_END(), }; enum smart_ops { OP_SET = 0, OP_INJECT, }; enum alarms { ALARM_ON = 1, ALARM_OFF, }; static inline void enable_set(void) { sctx.op_mask |= 1 << OP_SET; } static inline void enable_inject(void) { sctx.op_mask |= 1 << OP_INJECT; } #define smart_param_setup_uint(arg) \ { \ if (param.arg) { \ sctx.arg = strtoul(param.arg, NULL, 0); \ if (sctx.arg == ULONG_MAX || sctx.arg > UINT_MAX) { \ error("Invalid argument: %s: %s\n", #arg, param.arg); \ return -EINVAL; \ } \ enable_inject(); \ } \ if (param.arg##_threshold) { \ sctx.arg##_threshold = \ strtoul(param.arg##_threshold, NULL, 0); \ if (sctx.arg##_threshold == ULONG_MAX \ || sctx.arg##_threshold > UINT_MAX) { \ error("Invalid argument: %s\n", \ param.arg##_threshold); \ return -EINVAL; \ } \ enable_set(); \ } \ } #define smart_param_setup_temps(arg) \ { \ double temp; \ if (param.arg) { \ temp = strtod(param.arg, NULL); \ if (temp == HUGE_VAL || temp == -HUGE_VAL) { \ error("Invalid argument: %s: %s\n", #arg, param.arg); \ return -EINVAL; \ } \ sctx.arg = ndctl_encode_smart_temperature(temp); \ enable_inject(); \ } \ if (param.arg##_threshold) { \ temp = strtod(param.arg##_threshold, NULL); \ if (temp == HUGE_VAL || temp == -HUGE_VAL) { \ error("Invalid argument: %s\n", \ param.arg##_threshold); \ return -EINVAL; \ } \ sctx.arg##_threshold = ndctl_encode_smart_temperature(temp); \ enable_set(); \ } \ } #define smart_param_setup_alarm(arg) \ { \ if (param.arg##_alarm) { \ if (strncmp(param.arg##_alarm, "on", 2) == 0) \ sctx.arg##_alarm = ALARM_ON; \ else if (strncmp(param.arg##_alarm, "off", 3) == 0) \ sctx.arg##_alarm = ALARM_OFF; \ sctx.alarms_present = true; \ } \ } #define smart_param_setup_uninj(arg) \ { \ if (param.arg##_uninject) { \ /* Ensure user didn't set inject and uninject together */ \ if (param.arg) { \ error("Cannot use %s inject and uninject together\n", \ #arg); \ return -EINVAL; \ } \ /* Then set the inject flag so this can be accounted for */ \ param.arg = "0"; \ enable_inject(); \ } \ } static int smart_init(void) { if (param.human) sctx.flags |= UTIL_JSON_HUMAN; sctx.err_continue = false; /* setup attributes and thresholds except alarm_control */ smart_param_setup_temps(media_temperature) smart_param_setup_temps(ctrl_temperature) smart_param_setup_uint(spares) /* set up alarm_control */ smart_param_setup_alarm(media_temperature) smart_param_setup_alarm(ctrl_temperature) smart_param_setup_alarm(spares) if (sctx.alarms_present) enable_set(); /* setup remaining injection attributes */ if (param.fatal || param.unsafe_shutdown) enable_inject(); /* setup uninjections */ if (param.uninject_all) { param.media_temperature_uninject = true; param.ctrl_temperature_uninject = true; param.spares_uninject = true; param.fatal_uninject = true; param.unsafe_shutdown_uninject = true; sctx.err_continue = true; } smart_param_setup_uninj(media_temperature) smart_param_setup_uninj(ctrl_temperature) smart_param_setup_uninj(spares) smart_param_setup_uninj(fatal) smart_param_setup_uninj(unsafe_shutdown) if (sctx.op_mask == 0) { error("No valid operation specified\n"); return -EINVAL; } return 0; } #define setup_thresh_field(arg) \ { \ if (param.arg##_threshold) \ ndctl_cmd_smart_threshold_set_##arg(sst_cmd, \ sctx.arg##_threshold); \ } static int smart_set_thresh(struct ndctl_dimm *dimm) { const char *name = ndctl_dimm_get_devname(dimm); struct ndctl_cmd *st_cmd = NULL, *sst_cmd = NULL; int rc = -EOPNOTSUPP; st_cmd = ndctl_dimm_cmd_new_smart_threshold(dimm); if (!st_cmd) { error("%s: no smart threshold command support\n", name); goto out; } rc = ndctl_cmd_submit_xlat(st_cmd); if (rc < 0) { error("%s: smart threshold command failed: %s (%d)\n", name, strerror(abs(rc)), rc); goto out; } sst_cmd = ndctl_dimm_cmd_new_smart_set_threshold(st_cmd); if (!sst_cmd) { error("%s: no smart set threshold command support\n", name); rc = -EOPNOTSUPP; goto out; } /* setup all thresholds except alarm_control */ setup_thresh_field(media_temperature) setup_thresh_field(ctrl_temperature) setup_thresh_field(spares) /* setup alarm_control manually */ if (sctx.alarms_present) { unsigned int alarm; alarm = ndctl_cmd_smart_threshold_get_alarm_control(st_cmd); if (sctx.media_temperature_alarm == ALARM_ON) alarm |= ND_SMART_TEMP_TRIP; else if (sctx.media_temperature_alarm == ALARM_OFF) alarm &= ~ND_SMART_TEMP_TRIP; if (sctx.ctrl_temperature_alarm == ALARM_ON) alarm |= ND_SMART_CTEMP_TRIP; else if (sctx.ctrl_temperature_alarm == ALARM_OFF) alarm &= ~ND_SMART_CTEMP_TRIP; if (sctx.spares_alarm == ALARM_ON) alarm |= ND_SMART_SPARE_TRIP; else if (sctx.spares_alarm == ALARM_OFF) alarm &= ~ND_SMART_SPARE_TRIP; ndctl_cmd_smart_threshold_set_alarm_control(sst_cmd, alarm); } rc = ndctl_cmd_submit_xlat(sst_cmd); if (rc < 0) error("%s: smart set threshold command failed: %s (%d)\n", name, strerror(abs(rc)), rc); out: ndctl_cmd_unref(sst_cmd); ndctl_cmd_unref(st_cmd); return rc; } #define send_inject_val(arg) \ { \ if (param.arg) { \ bool enable = true; \ \ si_cmd = ndctl_dimm_cmd_new_smart_inject(dimm); \ if (!si_cmd) { \ error("%s: no smart inject command support\n", name); \ if (sctx.err_continue == false) \ goto out; \ } \ if (param.arg##_uninject) \ enable = false; \ rc = ndctl_cmd_smart_inject_##arg(si_cmd, enable, sctx.arg); \ if (rc) { \ error("%s: smart inject %s cmd invalid: %s (%d)\n", \ name, #arg, strerror(abs(rc)), rc); \ if (sctx.err_continue == false) \ goto out; \ } \ rc = ndctl_cmd_submit_xlat(si_cmd); \ if (rc < 0) { \ error("%s: smart inject %s command failed: %s (%d)\n", \ name, #arg, strerror(abs(rc)), rc); \ if (sctx.err_continue == false) \ goto out; \ } \ ndctl_cmd_unref(si_cmd); \ } \ } #define send_inject_bool(arg) \ { \ if (param.arg) { \ bool enable = true; \ \ si_cmd = ndctl_dimm_cmd_new_smart_inject(dimm); \ if (!si_cmd) { \ error("%s: no smart inject command support\n", name); \ if (sctx.err_continue == false) \ goto out; \ } \ if (param.arg##_uninject) \ enable = false; \ rc = ndctl_cmd_smart_inject_##arg(si_cmd, enable); \ if (rc) { \ error("%s: smart inject %s cmd invalid: %s (%d)\n", \ name, #arg, strerror(abs(rc)), rc); \ if (sctx.err_continue == false) \ goto out; \ } \ rc = ndctl_cmd_submit_xlat(si_cmd); \ if (rc < 0) { \ error("%s: smart inject %s command failed: %s (%d)\n", \ name, #arg, strerror(abs(rc)), rc); \ if (sctx.err_continue == false) \ goto out; \ } \ ndctl_cmd_unref(si_cmd); \ } \ } static int smart_inject(struct ndctl_dimm *dimm, unsigned int inject_types) { const char *name = ndctl_dimm_get_devname(dimm); struct ndctl_cmd *si_cmd = NULL; int rc = -EOPNOTSUPP; if (inject_types & ND_SMART_INJECT_MEDIA_TEMPERATURE) send_inject_val(media_temperature); if (inject_types & ND_SMART_INJECT_CTRL_TEMPERATURE) send_inject_val(ctrl_temperature); if (inject_types & ND_SMART_INJECT_SPARES_REMAINING) send_inject_val(spares); if (inject_types & ND_SMART_INJECT_HEALTH_STATE) send_inject_bool(fatal); if (inject_types & ND_SMART_INJECT_UNCLEAN_SHUTDOWN) send_inject_bool(unsafe_shutdown); out: ndctl_cmd_unref(si_cmd); return rc; } static int dimm_inject_smart(struct ndctl_dimm *dimm) { struct json_object *jhealth; struct json_object *jdimms; struct json_object *jdimm; unsigned int supported_types; int rc; rc = ndctl_dimm_smart_inject_supported(dimm); switch (rc) { case -ENOTTY: error("%s: smart injection not supported by ndctl.", ndctl_dimm_get_devname(dimm)); return rc; case -EOPNOTSUPP: error("%s: smart injection not supported by the kernel", ndctl_dimm_get_devname(dimm)); return rc; case -EIO: error("%s: smart injection not supported by either platform firmware or the kernel.", ndctl_dimm_get_devname(dimm)); return rc; default: if (rc < 0) { error("%s: Unknown error %d while checking for smart injection support", ndctl_dimm_get_devname(dimm), rc); return rc; } supported_types = rc; break; } if (sctx.op_mask & (1 << OP_SET)) { rc = smart_set_thresh(dimm); if (rc) goto out; } if (sctx.op_mask & (1 << OP_INJECT)) { rc = smart_inject(dimm, supported_types); if (rc) goto out; } if (rc == 0) { jdimms = json_object_new_array(); if (!jdimms) goto out; /* Ensure the dimm flags are upto date before reporting them */ ndctl_dimm_refresh_flags(dimm); jdimm = util_dimm_to_json(dimm, sctx.flags); if (!jdimm) goto out; json_object_array_add(jdimms, jdimm); jhealth = util_dimm_health_to_json(dimm); if (jhealth) { json_object_object_add(jdimm, "health", jhealth); util_display_json_array(stdout, jdimms, sctx.flags); } } out: return rc; } static int do_smart(const char *dimm_arg, struct ndctl_ctx *ctx) { struct ndctl_dimm *dimm; struct ndctl_bus *bus; int rc = -ENXIO; if (dimm_arg == NULL) return rc; if (param.verbose) ndctl_set_log_priority(ctx, LOG_DEBUG); ndctl_bus_foreach(ctx, bus) { if (!util_bus_filter(bus, param.bus)) continue; ndctl_dimm_foreach(bus, dimm) { if (!util_dimm_filter(dimm, dimm_arg)) continue; return dimm_inject_smart(dimm); } } error("%s: no such dimm\n", dimm_arg); return rc; } int cmd_inject_smart(int argc, const char **argv, struct ndctl_ctx *ctx) { const char * const u[] = { "ndctl inject-smart []", NULL }; int i, rc; argc = parse_options(argc, argv, smart_opts, u, 0); rc = smart_init(); if (rc) return rc; if (argc == 0) error("specify a dimm for the smart operation\n"); for (i = 1; i < argc; i++) error("unknown extra parameter \"%s\"\n", argv[i]); if (argc == 0 || argc > 1) { usage_with_options(u, smart_opts); return -ENODEV; /* we won't return from usage_with_options() */ } return do_smart(argv[0], ctx); } ndctl-81/ndctl/json-smart.c000066400000000000000000000131051476737544500160030ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2015-2020 Intel Corporation. All rights reserved. #include #include #include #include #include #include #include "json.h" static void smart_threshold_to_json(struct ndctl_dimm *dimm, struct json_object *jhealth) { unsigned int alarm_control; struct json_object *jobj; struct ndctl_cmd *cmd; int rc; cmd = ndctl_dimm_cmd_new_smart_threshold(dimm); if (!cmd) return; rc = ndctl_cmd_submit_xlat(cmd); if (rc < 0) goto out; alarm_control = ndctl_cmd_smart_threshold_get_alarm_control(cmd); if (alarm_control & ND_SMART_TEMP_TRIP) { unsigned int temp; double t; jobj = json_object_new_boolean(true); if (jobj) json_object_object_add(jhealth, "alarm_enabled_media_temperature", jobj); temp = ndctl_cmd_smart_threshold_get_temperature(cmd); t = ndctl_decode_smart_temperature(temp); jobj = json_object_new_double(t); if (jobj) json_object_object_add(jhealth, "temperature_threshold", jobj); } else { jobj = json_object_new_boolean(false); if (jobj) json_object_object_add(jhealth, "alarm_enabled_media_temperature", jobj); } if (alarm_control & ND_SMART_CTEMP_TRIP) { unsigned int temp; double t; jobj = json_object_new_boolean(true); if (jobj) json_object_object_add(jhealth, "alarm_enabled_ctrl_temperature", jobj); temp = ndctl_cmd_smart_threshold_get_ctrl_temperature(cmd); t = ndctl_decode_smart_temperature(temp); jobj = json_object_new_double(t); if (jobj) json_object_object_add(jhealth, "controller_temperature_threshold", jobj); } else { jobj = json_object_new_boolean(false); if (jobj) json_object_object_add(jhealth, "alarm_enabled_ctrl_temperature", jobj); } if (alarm_control & ND_SMART_SPARE_TRIP) { unsigned int spares; jobj = json_object_new_boolean(true); if (jobj) json_object_object_add(jhealth, "alarm_enabled_spares", jobj); spares = ndctl_cmd_smart_threshold_get_spares(cmd); jobj = json_object_new_int(spares); if (jobj) json_object_object_add(jhealth, "spares_threshold", jobj); } else { jobj = json_object_new_boolean(false); if (jobj) json_object_object_add(jhealth, "alarm_enabled_spares", jobj); } out: ndctl_cmd_unref(cmd); } struct json_object *util_dimm_health_to_json(struct ndctl_dimm *dimm) { struct json_object *jhealth = json_object_new_object(); struct json_object *jobj; struct ndctl_cmd *cmd; unsigned int flags; int rc; if (!jhealth) return NULL; cmd = ndctl_dimm_cmd_new_smart(dimm); if (!cmd) goto err; rc = ndctl_cmd_submit_xlat(cmd); if (rc < 0) { jobj = json_object_new_string("unknown"); if (jobj) json_object_object_add(jhealth, "health_state", jobj); goto out; } flags = ndctl_cmd_smart_get_flags(cmd); if (flags & ND_SMART_HEALTH_VALID) { unsigned int health = ndctl_cmd_smart_get_health(cmd); if (health & ND_SMART_FATAL_HEALTH) jobj = json_object_new_string("fatal"); else if (health & ND_SMART_CRITICAL_HEALTH) jobj = json_object_new_string("critical"); else if (health & ND_SMART_NON_CRITICAL_HEALTH) jobj = json_object_new_string("non-critical"); else jobj = json_object_new_string("ok"); if (jobj) json_object_object_add(jhealth, "health_state", jobj); } if (flags & ND_SMART_TEMP_VALID) { unsigned int temp = ndctl_cmd_smart_get_temperature(cmd); double t = ndctl_decode_smart_temperature(temp); jobj = json_object_new_double(t); if (jobj) json_object_object_add(jhealth, "temperature_celsius", jobj); } if (flags & ND_SMART_CTEMP_VALID) { unsigned int temp = ndctl_cmd_smart_get_ctrl_temperature(cmd); double t = ndctl_decode_smart_temperature(temp); jobj = json_object_new_double(t); if (jobj) json_object_object_add(jhealth, "controller_temperature_celsius", jobj); } if (flags & ND_SMART_SPARES_VALID) { unsigned int spares = ndctl_cmd_smart_get_spares(cmd); jobj = json_object_new_int(spares); if (jobj) json_object_object_add(jhealth, "spares_percentage", jobj); } if (flags & ND_SMART_ALARM_VALID) { unsigned int alarm_flags = ndctl_cmd_smart_get_alarm_flags(cmd); bool temp_flag = !!(alarm_flags & ND_SMART_TEMP_TRIP); bool ctrl_temp_flag = !!(alarm_flags & ND_SMART_CTEMP_TRIP); bool spares_flag = !!(alarm_flags & ND_SMART_SPARE_TRIP); jobj = json_object_new_boolean(temp_flag); if (jobj) json_object_object_add(jhealth, "alarm_temperature", jobj); jobj = json_object_new_boolean(ctrl_temp_flag); if (jobj) json_object_object_add(jhealth, "alarm_controller_temperature", jobj); jobj = json_object_new_boolean(spares_flag); if (jobj) json_object_object_add(jhealth, "alarm_spares", jobj); } smart_threshold_to_json(dimm, jhealth); if (flags & ND_SMART_USED_VALID) { unsigned int life_used = ndctl_cmd_smart_get_life_used(cmd); jobj = json_object_new_int(life_used); if (jobj) json_object_object_add(jhealth, "life_used_percentage", jobj); } if (flags & ND_SMART_SHUTDOWN_VALID) { unsigned int shutdown = ndctl_cmd_smart_get_shutdown_state(cmd); jobj = json_object_new_string(shutdown ? "dirty" : "clean"); if (jobj) json_object_object_add(jhealth, "shutdown_state", jobj); } if (flags & ND_SMART_SHUTDOWN_COUNT_VALID) { unsigned int shutdown = ndctl_cmd_smart_get_shutdown_count(cmd); jobj = json_object_new_int(shutdown); if (jobj) json_object_object_add(jhealth, "shutdown_count", jobj); } ndctl_cmd_unref(cmd); return jhealth; err: json_object_put(jhealth); jhealth = NULL; out: if (cmd) ndctl_cmd_unref(cmd); return jhealth; } ndctl-81/ndctl/json.c000066400000000000000000000640761476737544500146740ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2015-2020 Intel Corporation. All rights reserved. #include #include #include #include #include #include #include #include "json.h" #include "ndctl.h" #include "../daxctl/json.h" struct json_object *util_bus_to_json(struct ndctl_bus *bus, unsigned long flags) { struct json_object *jbus = json_object_new_object(); struct json_object *jobj, *fw_obj = NULL; int scrub; if (!jbus) return NULL; jobj = json_object_new_string(ndctl_bus_get_provider(bus)); if (!jobj) goto err; json_object_object_add(jbus, "provider", jobj); jobj = json_object_new_string(ndctl_bus_get_devname(bus)); if (!jobj) goto err; json_object_object_add(jbus, "dev", jobj); scrub = ndctl_bus_get_scrub_state(bus); if (scrub < 0) return jbus; jobj = json_object_new_string(scrub ? "active" : "idle"); if (!jobj) goto err; json_object_object_add(jbus, "scrub_state", jobj); if (flags & UTIL_JSON_FIRMWARE) { struct ndctl_dimm *dimm; /* * Skip displaying firmware activation capability if no * DIMMs support firmware update. */ ndctl_dimm_foreach(bus, dimm) if (ndctl_dimm_fw_update_supported(dimm) == 0) { fw_obj = json_object_new_object(); break; } } if (fw_obj) { enum ndctl_fwa_state state; enum ndctl_fwa_method method; jobj = NULL; method = ndctl_bus_get_fw_activate_method(bus); if (method == NDCTL_FWA_METHOD_RESET) jobj = json_object_new_string("reset"); if (method == NDCTL_FWA_METHOD_SUSPEND) jobj = json_object_new_string("suspend"); if (method == NDCTL_FWA_METHOD_LIVE) jobj = json_object_new_string("live"); if (jobj) json_object_object_add(fw_obj, "activate_method", jobj); jobj = NULL; state = ndctl_bus_get_fw_activate_state(bus); if (state == NDCTL_FWA_ARMED) jobj = json_object_new_string("armed"); if (state == NDCTL_FWA_IDLE) jobj = json_object_new_string("idle"); if (state == NDCTL_FWA_ARM_OVERFLOW) jobj = json_object_new_string("overflow"); if (jobj) json_object_object_add(fw_obj, "activate_state", jobj); json_object_object_add(jbus, "firmware", fw_obj); } return jbus; err: json_object_put(jbus); return NULL; } struct json_object *util_dimm_firmware_to_json(struct ndctl_dimm *dimm, unsigned long flags) { struct json_object *jfirmware = json_object_new_object(); bool can_update, need_powercycle; enum ndctl_fwa_result result; enum ndctl_fwa_state state; struct json_object *jobj; struct ndctl_cmd *cmd; uint64_t run, next; int rc; if (!jfirmware) return NULL; cmd = ndctl_dimm_cmd_new_fw_get_info(dimm); if (!cmd) goto err; rc = ndctl_cmd_submit(cmd); if ((rc < 0) || ndctl_cmd_fw_xlat_firmware_status(cmd) != FW_SUCCESS) { jobj = util_json_object_hex(-1, flags); if (jobj) json_object_object_add(jfirmware, "current_version", jobj); goto out; } run = ndctl_cmd_fw_info_get_run_version(cmd); if (run == ULLONG_MAX) { jobj = util_json_object_hex(-1, flags); if (jobj) json_object_object_add(jfirmware, "current_version", jobj); goto out; } jobj = util_json_object_hex(run, flags); if (jobj) json_object_object_add(jfirmware, "current_version", jobj); rc = ndctl_dimm_fw_update_supported(dimm); can_update = rc == 0; jobj = json_object_new_boolean(can_update); if (jobj) json_object_object_add(jfirmware, "can_update", jobj); next = ndctl_cmd_fw_info_get_updated_version(cmd); if (next == ULLONG_MAX) { jobj = util_json_object_hex(-1, flags); if (jobj) json_object_object_add(jfirmware, "next_version", jobj); goto out; } if (!next) goto out; jobj = util_json_object_hex(next, flags); if (jobj) json_object_object_add(jfirmware, "next_version", jobj); state = ndctl_dimm_get_fw_activate_state(dimm); switch (state) { case NDCTL_FWA_IDLE: jobj = json_object_new_string("idle"); break; case NDCTL_FWA_ARMED: jobj = json_object_new_string("armed"); break; case NDCTL_FWA_BUSY: jobj = json_object_new_string("busy"); break; default: jobj = NULL; break; } if (jobj) json_object_object_add(jfirmware, "activate_state", jobj); result = ndctl_dimm_get_fw_activate_result(dimm); switch (result) { case NDCTL_FWA_RESULT_NONE: case NDCTL_FWA_RESULT_SUCCESS: case NDCTL_FWA_RESULT_NOTSTAGED: /* * If a 'next' firmware version is staged then this * result is stale, if the activation succeeds that is * indicated by not finding a 'next' entry. */ need_powercycle = false; break; case NDCTL_FWA_RESULT_NEEDRESET: case NDCTL_FWA_RESULT_FAIL: default: /* * If the last activation failed, or if the activation * result is unavailable it is always the case that the * only remediation is powercycle. */ need_powercycle = true; break; } if (need_powercycle) { jobj = json_object_new_boolean(true); if (!jobj) goto out; json_object_object_add(jfirmware, "need_powercycle", jobj); } ndctl_cmd_unref(cmd); return jfirmware; err: json_object_put(jfirmware); jfirmware = NULL; out: if (cmd) ndctl_cmd_unref(cmd); return jfirmware; } struct json_object *util_dimm_to_json(struct ndctl_dimm *dimm, unsigned long flags) { struct json_object *jdimm = json_object_new_object(); const char *id = ndctl_dimm_get_unique_id(dimm); unsigned int handle = ndctl_dimm_get_handle(dimm); unsigned short phys_id = ndctl_dimm_get_phys_id(dimm); struct json_object *jobj; enum ndctl_security_state sstate; if (!jdimm) return NULL; jobj = json_object_new_string(ndctl_dimm_get_devname(dimm)); if (!jobj) goto err; json_object_object_add(jdimm, "dev", jobj); if (id) { jobj = json_object_new_string(id); if (!jobj) goto err; json_object_object_add(jdimm, "id", jobj); } if (handle < UINT_MAX) { jobj = util_json_object_hex(handle, flags); if (!jobj) goto err; json_object_object_add(jdimm, "handle", jobj); } if (phys_id < USHRT_MAX) { jobj = util_json_object_hex(phys_id, flags); if (!jobj) goto err; json_object_object_add(jdimm, "phys_id", jobj); } if (!ndctl_dimm_is_enabled(dimm)) { jobj = json_object_new_string("disabled"); if (!jobj) goto err; json_object_object_add(jdimm, "state", jobj); } if (ndctl_dimm_failed_map(dimm)) { jobj = json_object_new_boolean(true); if (!jobj) goto err; json_object_object_add(jdimm, "flag_failed_map", jobj); } if (ndctl_dimm_failed_save(dimm)) { jobj = json_object_new_boolean(true); if (!jobj) goto err; json_object_object_add(jdimm, "flag_failed_save", jobj); } if (ndctl_dimm_failed_arm(dimm)) { jobj = json_object_new_boolean(true); if (!jobj) goto err; json_object_object_add(jdimm, "flag_failed_arm", jobj); } if (ndctl_dimm_failed_restore(dimm)) { jobj = json_object_new_boolean(true); if (!jobj) goto err; json_object_object_add(jdimm, "flag_failed_restore", jobj); } if (ndctl_dimm_failed_flush(dimm)) { jobj = json_object_new_boolean(true); if (!jobj) goto err; json_object_object_add(jdimm, "flag_failed_flush", jobj); } if (ndctl_dimm_smart_pending(dimm)) { jobj = json_object_new_boolean(true); if (!jobj) goto err; json_object_object_add(jdimm, "flag_smart_event", jobj); } sstate = ndctl_dimm_get_security(dimm); if (sstate == NDCTL_SECURITY_DISABLED) jobj = json_object_new_string("disabled"); else if (sstate == NDCTL_SECURITY_UNLOCKED) jobj = json_object_new_string("unlocked"); else if (sstate == NDCTL_SECURITY_LOCKED) jobj = json_object_new_string("locked"); else if (sstate == NDCTL_SECURITY_FROZEN) jobj = json_object_new_string("frozen"); else if (sstate == NDCTL_SECURITY_OVERWRITE) jobj = json_object_new_string("overwrite"); else jobj = NULL; if (jobj) json_object_object_add(jdimm, "security", jobj); if (ndctl_dimm_security_is_frozen(dimm)) { jobj = json_object_new_boolean(true); if (jobj) json_object_object_add(jdimm, "security_frozen", jobj); } if (flags & UTIL_JSON_FIRMWARE) { struct json_object *jfirmware; jfirmware = util_dimm_firmware_to_json(dimm, flags); if (jfirmware) json_object_object_add(jdimm, "firmware", jfirmware); } return jdimm; err: json_object_put(jdimm); return NULL; } #define _SZ(get_max, get_elem, type) \ static struct json_object *util_##type##_build_size_array(struct ndctl_##type *arg) \ { \ struct json_object *arr = json_object_new_array(); \ int i; \ \ if (!arr) \ return NULL; \ \ for (i = 0; i < get_max(arg); i++) { \ struct json_object *jobj; \ int64_t align; \ \ align = get_elem(arg, i); \ jobj = util_json_new_u64(align); \ if (!jobj) \ goto err; \ json_object_array_add(arr, jobj); \ } \ \ return arr; \ err: \ json_object_put(arr); \ return NULL; \ } #define SZ(type, kind) _SZ(ndctl_##type##_get_num_##kind##s, \ ndctl_##type##_get_supported_##kind, type) SZ(pfn, alignment) SZ(dax, alignment) SZ(btt, sector_size) struct json_object *util_region_capabilities_to_json(struct ndctl_region *region) { struct json_object *jcaps, *jcap, *jobj; struct ndctl_btt *btt = ndctl_region_get_btt_seed(region); struct ndctl_pfn *pfn = ndctl_region_get_pfn_seed(region); struct ndctl_dax *dax = ndctl_region_get_dax_seed(region); if (!btt && !pfn && !dax) return NULL; jcaps = json_object_new_array(); if (!jcaps) return NULL; if (btt) { jcap = json_object_new_object(); if (!jcap) goto err; json_object_array_add(jcaps, jcap); jobj = json_object_new_string("sector"); if (!jobj) goto err; json_object_object_add(jcap, "mode", jobj); jobj = util_btt_build_size_array(btt); if (!jobj) goto err; json_object_object_add(jcap, "sector_sizes", jobj); } if (pfn) { jcap = json_object_new_object(); if (!jcap) goto err; json_object_array_add(jcaps, jcap); jobj = json_object_new_string("fsdax"); if (!jobj) goto err; json_object_object_add(jcap, "mode", jobj); jobj = util_pfn_build_size_array(pfn); if (!jobj) goto err; json_object_object_add(jcap, "alignments", jobj); } if (dax) { jcap = json_object_new_object(); if (!jcap) goto err; json_object_array_add(jcaps, jcap); jobj = json_object_new_string("devdax"); if (!jobj) goto err; json_object_object_add(jcap, "mode", jobj); jobj = util_dax_build_size_array(dax); if (!jobj) goto err; json_object_object_add(jcap, "alignments", jobj); } return jcaps; err: json_object_put(jcaps); return NULL; } static int compare_dimm_number(const void *p1, const void *p2) { struct ndctl_dimm *dimm1 = *(struct ndctl_dimm **)p1; struct ndctl_dimm *dimm2 = *(struct ndctl_dimm **)p2; const char *dimm1_name = ndctl_dimm_get_devname(dimm1); const char *dimm2_name = ndctl_dimm_get_devname(dimm2); int num1, num2; if (sscanf(dimm1_name, "nmem%d", &num1) != 1) num1 = 0; if (sscanf(dimm2_name, "nmem%d", &num2) != 1) num2 = 0; return num1 - num2; } static struct json_object *badblocks_to_jdimms(struct ndctl_region *region, unsigned long long addr, unsigned long len) { struct ndctl_bus *bus = ndctl_region_get_bus(region); int count = ndctl_region_get_interleave_ways(region); unsigned long long end = addr + len; struct json_object *jdimms, *jobj; struct ndctl_dimm **dimms, *dimm; int found, i; jdimms = json_object_new_array(); if (!jdimms) return NULL; dimms = calloc(count, sizeof(struct ndctl_dimm *)); if (!dimms) goto err_dimms; for (found = 0; found < count && addr < end; addr += 512) { dimm = ndctl_bus_get_dimm_by_physical_address(bus, addr); if (!dimm) continue; for (i = 0; i < count; i++) if (dimms[i] == dimm) break; if (i >= count) dimms[found++] = dimm; } if (!found) goto err_found; qsort(dimms, found, sizeof(dimm), compare_dimm_number); for (i = 0; i < found; i++) { const char *devname = ndctl_dimm_get_devname(dimms[i]); jobj = json_object_new_string(devname); if (!jobj) break; json_object_array_add(jdimms, jobj); } if (!i) goto err_found; free(dimms); return jdimms; err_found: free(dimms); err_dimms: json_object_put(jdimms); return NULL; } struct json_object *util_region_badblocks_to_json(struct ndctl_region *region, unsigned int *bb_count, unsigned long flags) { struct json_object *jbb = NULL, *jbbs = NULL, *jobj; struct badblock *bb; int bbs = 0; if (flags & UTIL_JSON_MEDIA_ERRORS) { jbbs = json_object_new_array(); if (!jbbs) return NULL; } ndctl_region_badblock_foreach(region, bb) { struct json_object *jdimms; unsigned long long addr; bbs += bb->len; /* recheck so we can still get the badblocks_count from above */ if (!(flags & UTIL_JSON_MEDIA_ERRORS)) continue; /* get start address of region */ addr = ndctl_region_get_resource(region); if (addr == ULLONG_MAX) goto err_array; /* get address of bad block */ addr += bb->offset << 9; jbb = json_object_new_object(); if (!jbb) goto err_array; jobj = util_json_new_u64(bb->offset); if (!jobj) goto err; json_object_object_add(jbb, "offset", jobj); jobj = json_object_new_int(bb->len); if (!jobj) goto err; json_object_object_add(jbb, "length", jobj); jdimms = badblocks_to_jdimms(region, addr, bb->len << 9); if (jdimms) json_object_object_add(jbb, "dimms", jdimms); json_object_array_add(jbbs, jbb); } *bb_count = bbs; if (bbs) return jbbs; err: json_object_put(jbb); err_array: json_object_put(jbbs); return NULL; } static struct json_object *util_namespace_badblocks_to_json( struct ndctl_namespace *ndns, unsigned int *bb_count, unsigned long flags) { struct json_object *jbb = NULL, *jbbs = NULL, *jobj; struct badblock *bb; int bbs = 0; if (flags & UTIL_JSON_MEDIA_ERRORS) { jbbs = json_object_new_array(); if (!jbbs) return NULL; } else return NULL; ndctl_namespace_badblock_foreach(ndns, bb) { bbs += bb->len; /* recheck so we can still get the badblocks_count from above */ if (!(flags & UTIL_JSON_MEDIA_ERRORS)) continue; jbb = json_object_new_object(); if (!jbb) goto err_array; jobj = util_json_new_u64(bb->offset); if (!jobj) goto err; json_object_object_add(jbb, "offset", jobj); jobj = json_object_new_int(bb->len); if (!jobj) goto err; json_object_object_add(jbb, "length", jobj); json_object_array_add(jbbs, jbb); } *bb_count = bbs; if (bbs) return jbbs; err: json_object_put(jbb); err_array: json_object_put(jbbs); return NULL; } static struct json_object *dev_badblocks_to_json(struct ndctl_region *region, unsigned long long dev_begin, unsigned long long dev_size, unsigned int *bb_count, unsigned long flags) { struct json_object *jbb = NULL, *jbbs = NULL, *jobj; unsigned long long region_begin, dev_end, offset; unsigned int len, bbs = 0; struct badblock *bb; region_begin = ndctl_region_get_resource(region); if (region_begin == ULLONG_MAX) return NULL; dev_end = dev_begin + dev_size - 1; if (flags & UTIL_JSON_MEDIA_ERRORS) { jbbs = json_object_new_array(); if (!jbbs) return NULL; } ndctl_region_badblock_foreach(region, bb) { unsigned long long bb_begin, bb_end, begin, end; struct json_object *jdimms; bb_begin = region_begin + (bb->offset << 9); bb_end = bb_begin + (bb->len << 9) - 1; if (bb_end <= dev_begin || bb_begin >= dev_end) continue; if (bb_begin < dev_begin) begin = dev_begin; else begin = bb_begin; if (bb_end > dev_end) end = dev_end; else end = bb_end; offset = (begin - dev_begin) >> 9; len = (end - begin + 1) >> 9; bbs += len; /* recheck so we can still get the badblocks_count from above */ if (!(flags & UTIL_JSON_MEDIA_ERRORS)) continue; jbb = json_object_new_object(); if (!jbb) goto err_array; jobj = util_json_new_u64(offset); if (!jobj) goto err; json_object_object_add(jbb, "offset", jobj); jobj = json_object_new_int(len); if (!jobj) goto err; json_object_object_add(jbb, "length", jobj); jdimms = badblocks_to_jdimms(region, begin, len << 9); if (jdimms) json_object_object_add(jbb, "dimms", jdimms); json_object_array_add(jbbs, jbb); } *bb_count = bbs; if (bbs) return jbbs; err: json_object_put(jbb); err_array: json_object_put(jbbs); return NULL; } static struct json_object *util_pfn_badblocks_to_json(struct ndctl_pfn *pfn, unsigned int *bb_count, unsigned long flags) { struct ndctl_region *region = ndctl_pfn_get_region(pfn); unsigned long long pfn_begin, pfn_size; pfn_begin = ndctl_pfn_get_resource(pfn); if (pfn_begin == ULLONG_MAX) { struct ndctl_namespace *ndns = ndctl_pfn_get_namespace(pfn); return util_namespace_badblocks_to_json(ndns, bb_count, flags); } pfn_size = ndctl_pfn_get_size(pfn); if (pfn_size == ULLONG_MAX) return NULL; return dev_badblocks_to_json(region, pfn_begin, pfn_size, bb_count, flags); } static void util_btt_badblocks_to_json(struct ndctl_btt *btt, unsigned int *bb_count) { struct ndctl_region *region = ndctl_btt_get_region(btt); struct ndctl_namespace *ndns = ndctl_btt_get_namespace(btt); unsigned long long begin, size; if (!ndns) return; begin = ndctl_namespace_get_resource(ndns); if (begin == ULLONG_MAX) return; size = ndctl_namespace_get_size(ndns); if (size == ULLONG_MAX) return; /* * The dev_badblocks_to_json() for BTT is not accurate with * respect to data vs metadata badblocks, and is only useful for * a potential bb_count. * * FIXME: switch to native BTT badblocks representation * when / if the kernel provides it. */ dev_badblocks_to_json(region, begin, size, bb_count, 0); } static struct json_object *util_dax_badblocks_to_json(struct ndctl_dax *dax, unsigned int *bb_count, unsigned long flags) { struct ndctl_region *region = ndctl_dax_get_region(dax); unsigned long long dax_begin, dax_size; dax_begin = ndctl_dax_get_resource(dax); if (dax_begin == ULLONG_MAX) return NULL; dax_size = ndctl_dax_get_size(dax); if (dax_size == ULLONG_MAX) return NULL; return dev_badblocks_to_json(region, dax_begin, dax_size, bb_count, flags); } static struct json_object *util_raw_uuid(struct ndctl_namespace *ndns) { char buf[40]; uuid_t raw_uuid; ndctl_namespace_get_uuid(ndns, raw_uuid); if (uuid_is_null(raw_uuid)) return NULL; uuid_unparse(raw_uuid, buf); return json_object_new_string(buf); } static void util_raw_uuid_to_json(struct ndctl_namespace *ndns, unsigned long flags, struct json_object *jndns) { struct json_object *jobj; if (!(flags & UTIL_JSON_VERBOSE)) return; jobj = util_raw_uuid(ndns); if (!jobj) return; json_object_object_add(jndns, "raw_uuid", jobj); } struct json_object *util_namespace_to_json(struct ndctl_namespace *ndns, unsigned long flags) { struct json_object *jndns = json_object_new_object(); enum ndctl_pfn_loc loc = NDCTL_PFN_LOC_NONE; struct json_object *jobj, *jbbs = NULL; const char *locations[] = { [NDCTL_PFN_LOC_NONE] = "none", [NDCTL_PFN_LOC_RAM] = "mem", [NDCTL_PFN_LOC_PMEM] = "dev", }; unsigned long long size = ULLONG_MAX; unsigned int sector_size = UINT_MAX; enum ndctl_namespace_mode mode; const char *bdev = NULL, *name; unsigned int bb_count = 0; struct ndctl_btt *btt; struct ndctl_pfn *pfn; struct ndctl_dax *dax; unsigned long align = 0; char buf[40]; uuid_t uuid; int numa, target; if (!jndns) return NULL; jobj = json_object_new_string(ndctl_namespace_get_devname(ndns)); if (!jobj) goto err; json_object_object_add(jndns, "dev", jobj); btt = ndctl_namespace_get_btt(ndns); dax = ndctl_namespace_get_dax(ndns); pfn = ndctl_namespace_get_pfn(ndns); mode = ndctl_namespace_get_mode(ndns); switch (mode) { case NDCTL_NS_MODE_MEMORY: if (pfn) { /* dynamic memory mode */ size = ndctl_pfn_get_size(pfn); loc = ndctl_pfn_get_location(pfn); } else { /* native/static memory mode */ size = ndctl_namespace_get_size(ndns); loc = NDCTL_PFN_LOC_RAM; } jobj = json_object_new_string("fsdax"); break; case NDCTL_NS_MODE_DAX: if (!dax) goto err; size = ndctl_dax_get_size(dax); jobj = json_object_new_string("devdax"); loc = ndctl_dax_get_location(dax); break; case NDCTL_NS_MODE_SECTOR: if (!btt) goto err; jobj = json_object_new_string("sector"); size = ndctl_btt_get_size(btt); break; case NDCTL_NS_MODE_RAW: size = ndctl_namespace_get_size(ndns); jobj = json_object_new_string("raw"); break; default: jobj = NULL; } if (jobj) json_object_object_add(jndns, "mode", jobj); if ((mode != NDCTL_NS_MODE_SECTOR) && (mode != NDCTL_NS_MODE_RAW)) { jobj = json_object_new_string(locations[loc]); if (jobj) json_object_object_add(jndns, "map", jobj); } if (size < ULLONG_MAX) { jobj = util_json_object_size(size, flags); if (jobj) json_object_object_add(jndns, "size", jobj); } if (btt) { ndctl_btt_get_uuid(btt, uuid); uuid_unparse(uuid, buf); jobj = json_object_new_string(buf); if (!jobj) goto err; json_object_object_add(jndns, "uuid", jobj); util_raw_uuid_to_json(ndns, flags, jndns); bdev = ndctl_btt_get_block_device(btt); } else if (pfn) { align = ndctl_pfn_get_align(pfn); ndctl_pfn_get_uuid(pfn, uuid); uuid_unparse(uuid, buf); jobj = json_object_new_string(buf); if (!jobj) goto err; json_object_object_add(jndns, "uuid", jobj); util_raw_uuid_to_json(ndns, flags, jndns); bdev = ndctl_pfn_get_block_device(pfn); } else if (dax) { struct daxctl_region *dax_region; dax_region = ndctl_dax_get_daxctl_region(dax); align = ndctl_dax_get_align(dax); ndctl_dax_get_uuid(dax, uuid); uuid_unparse(uuid, buf); jobj = json_object_new_string(buf); if (!jobj) goto err; json_object_object_add(jndns, "uuid", jobj); util_raw_uuid_to_json(ndns, flags, jndns); if ((flags & UTIL_JSON_DAX) && dax_region) { jobj = util_daxctl_region_to_json(dax_region, NULL, flags); if (jobj) json_object_object_add(jndns, "daxregion", jobj); } else if (dax_region) { struct daxctl_dev *dev; /* * We can only find/list these device-dax * details when the instance is enabled. */ dev = daxctl_dev_get_first(dax_region); if (dev) { name = daxctl_dev_get_devname(dev); jobj = json_object_new_string(name); if (!jobj) goto err; json_object_object_add(jndns, "chardev", jobj); } } } else if (ndctl_namespace_get_type(ndns) != ND_DEVICE_NAMESPACE_IO) { ndctl_namespace_get_uuid(ndns, uuid); uuid_unparse(uuid, buf); jobj = json_object_new_string(buf); if (!jobj) goto err; json_object_object_add(jndns, "uuid", jobj); bdev = ndctl_namespace_get_block_device(ndns); } else bdev = ndctl_namespace_get_block_device(ndns); if (btt) sector_size = ndctl_btt_get_sector_size(btt); else if (!dax) { sector_size = ndctl_namespace_get_sector_size(ndns); if (!sector_size || sector_size == UINT_MAX) sector_size = 512; } /* * The kernel will default to a 512 byte sector size on PMEM * namespaces that don't explicitly have a sector size. This * happens because they use pre-v1.2 labels or because they * don't have a label space (devtype=nd_namespace_io). */ if (sector_size < UINT_MAX) { jobj = json_object_new_int(sector_size); if (!jobj) goto err; json_object_object_add(jndns, "sector_size", jobj); } if (align) { jobj = util_json_new_u64(align); if (!jobj) goto err; json_object_object_add(jndns, "align", jobj); } if (bdev && bdev[0]) { jobj = json_object_new_string(bdev); if (!jobj) goto err; json_object_object_add(jndns, "blockdev", jobj); } if (!ndctl_namespace_is_active(ndns)) { jobj = json_object_new_string("disabled"); if (!jobj) goto err; json_object_object_add(jndns, "state", jobj); } name = ndctl_namespace_get_alt_name(ndns); if (name && name[0]) { jobj = json_object_new_string(name); if (!jobj) goto err; json_object_object_add(jndns, "name", jobj); } numa = ndctl_namespace_get_numa_node(ndns); if (numa >= 0 && flags & UTIL_JSON_VERBOSE) { jobj = json_object_new_int(numa); if (jobj) json_object_object_add(jndns, "numa_node", jobj); } target = ndctl_namespace_get_target_node(ndns); if (target >= 0 && flags & UTIL_JSON_VERBOSE) { jobj = json_object_new_int(target); if (jobj) json_object_object_add(jndns, "target_node", jobj); } if (pfn) jbbs = util_pfn_badblocks_to_json(pfn, &bb_count, flags); else if (dax) jbbs = util_dax_badblocks_to_json(dax, &bb_count, flags); else if (btt) util_btt_badblocks_to_json(btt, &bb_count); else { jbbs = util_region_badblocks_to_json( ndctl_namespace_get_region(ndns), &bb_count, flags); if (!jbbs) jbbs = util_namespace_badblocks_to_json(ndns, &bb_count, flags); } if (bb_count) { jobj = json_object_new_int(bb_count); if (!jobj) { json_object_put(jbbs); goto err; } json_object_object_add(jndns, "badblock_count", jobj); } if ((flags & UTIL_JSON_MEDIA_ERRORS) && jbbs) json_object_object_add(jndns, "badblocks", jbbs); return jndns; err: json_object_put(jndns); return NULL; } struct json_object *util_mapping_to_json(struct ndctl_mapping *mapping, unsigned long flags) { struct json_object *jmapping = json_object_new_object(); struct ndctl_dimm *dimm = ndctl_mapping_get_dimm(mapping); struct json_object *jobj; int position; if (!jmapping) return NULL; jobj = json_object_new_string(ndctl_dimm_get_devname(dimm)); if (!jobj) goto err; json_object_object_add(jmapping, "dimm", jobj); jobj = util_json_object_hex(ndctl_mapping_get_offset(mapping), flags); if (!jobj) goto err; json_object_object_add(jmapping, "offset", jobj); jobj = util_json_object_hex(ndctl_mapping_get_length(mapping), flags); if (!jobj) goto err; json_object_object_add(jmapping, "length", jobj); position = ndctl_mapping_get_position(mapping); if (position >= 0) { jobj = json_object_new_int(position); if (!jobj) goto err; json_object_object_add(jmapping, "position", jobj); } return jmapping; err: json_object_put(jmapping); return NULL; } struct json_object *util_badblock_rec_to_json(u64 block, u64 count, unsigned long flags) { struct json_object *jerr = json_object_new_object(); struct json_object *jobj; if (!jerr) return NULL; jobj = util_json_object_hex(block, flags); if (!jobj) goto err; json_object_object_add(jerr, "block", jobj); jobj = util_json_object_hex(count, flags); if (!jobj) goto err; json_object_object_add(jerr, "count", jobj); return jerr; err: json_object_put(jerr); return NULL; } ndctl-81/ndctl/json.h000066400000000000000000000021241476737544500146630ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (C) 2015-2020 Intel Corporation. All rights reserved. */ #ifndef __NDCTL_UTIL_JSON_H__ #define __NDCTL_UTIL_JSON_H__ #include #include struct json_object *util_namespace_to_json(struct ndctl_namespace *ndns, unsigned long flags); struct json_object *util_badblock_rec_to_json(u64 block, u64 count, unsigned long flags); struct json_object *util_region_badblocks_to_json(struct ndctl_region *region, unsigned int *bb_count, unsigned long flags); struct json_object *util_bus_to_json(struct ndctl_bus *bus, unsigned long flags); struct json_object *util_dimm_to_json(struct ndctl_dimm *dimm, unsigned long flags); struct json_object *util_mapping_to_json(struct ndctl_mapping *mapping, unsigned long flags); struct json_object *util_dimm_health_to_json(struct ndctl_dimm *dimm); struct json_object *util_dimm_firmware_to_json(struct ndctl_dimm *dimm, unsigned long flags); struct json_object *util_region_capabilities_to_json(struct ndctl_region *region); #endif /* __NDCTL_UTIL_JSON_H__ */ ndctl-81/ndctl/keys.c000066400000000000000000000327441476737544500146730ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) 2018-2020 Intel Corporation. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "keys.h" static int get_key_path(struct ndctl_dimm *dimm, char *path, enum ndctl_key_type key_type) { char hostname[HOST_NAME_MAX]; int rc; rc = gethostname(hostname, HOST_NAME_MAX); if (rc < 0) { fprintf(stderr, "gethostname: %s\n", strerror(errno)); return -errno; } switch (key_type) { case ND_USER_OLD_KEY: rc = sprintf(path, "%s/nvdimm-old_%s_%s.blob", NDCTL_KEYS_DIR, ndctl_dimm_get_unique_id(dimm), hostname); break; case ND_USER_KEY: rc = sprintf(path, "%s/nvdimm_%s_%s.blob", NDCTL_KEYS_DIR, ndctl_dimm_get_unique_id(dimm), hostname); break; case ND_MASTER_OLD_KEY: rc = sprintf(path, "%s/nvdimm-master-old_%s_%s.blob", NDCTL_KEYS_DIR, ndctl_dimm_get_unique_id(dimm), hostname); break; case ND_MASTER_KEY: rc = sprintf(path, "%s/nvdimm-master_%s_%s.blob", NDCTL_KEYS_DIR, ndctl_dimm_get_unique_id(dimm), hostname); break; default: return -EINVAL; } if (rc < 0) { fprintf(stderr, "error setting path: %s\n", strerror(errno)); return -errno; } return 0; } static int get_key_desc(struct ndctl_dimm *dimm, char *desc, enum ndctl_key_type key_type) { int rc; switch (key_type) { case ND_USER_OLD_KEY: rc = sprintf(desc, "nvdimm-old:%s", ndctl_dimm_get_unique_id(dimm)); break; case ND_USER_KEY: rc = sprintf(desc, "nvdimm:%s", ndctl_dimm_get_unique_id(dimm)); break; case ND_MASTER_OLD_KEY: rc = sprintf(desc, "nvdimm-master-old:%s", ndctl_dimm_get_unique_id(dimm)); break; case ND_MASTER_KEY: rc = sprintf(desc, "nvdimm-master:%s", ndctl_dimm_get_unique_id(dimm)); break; default: return -EINVAL; } if (rc < 0) { fprintf(stderr, "error setting key description: %s\n", strerror(errno)); return -errno; } return 0; } char *ndctl_load_key_blob(const char *path, int *size, const char *postfix, int dirfd, enum key_type key_type) { struct stat st; ssize_t read_bytes = 0; int rc, fd; char *blob = NULL, *pl, *rdptr; char prefix[] = "load "; bool need_prefix = false; if (key_type == KEY_ENCRYPTED || key_type == KEY_TRUSTED) need_prefix = true; fd = openat(dirfd, path, O_RDONLY); if (fd < 0) { fprintf(stderr, "failed to open file %s: %s\n", path, strerror(errno)); return NULL; } rc = fstat(fd, &st); if (rc < 0) { fprintf(stderr, "stat: %s\n", strerror(errno)); goto out_close; } if ((st.st_mode & S_IFMT) != S_IFREG) { fprintf(stderr, "%s not a regular file\n", path); goto out_close; } if (st.st_size == 0 || st.st_size > 4096) { fprintf(stderr, "Invalid blob file size\n"); goto out_close; } *size = st.st_size; if (need_prefix) *size += strlen(prefix); /* * We need to increment postfix and space. * "keyhandle=" is 10 bytes, plus null termination. */ if (postfix) *size += strlen(postfix) + 10 + 1; blob = malloc(*size); if (!blob) { fprintf(stderr, "Unable to allocate memory for blob\n"); return NULL; } if (need_prefix) { memcpy(blob, prefix, strlen(prefix)); pl = blob + strlen(prefix); } else pl = blob; rdptr = pl; do { rc = read(fd, rdptr, st.st_size - read_bytes); if (rc < 0) { fprintf(stderr, "Failed to read from blob file: %s\n", strerror(errno)); free(blob); blob = NULL; goto out_close; } read_bytes += rc; rdptr += rc; } while (read_bytes != st.st_size); if (postfix) { pl += read_bytes; *pl = ' '; pl++; rc = sprintf(pl, "keyhandle=%s", postfix); } out_close: close(fd); return blob; } static key_serial_t dimm_check_key(struct ndctl_dimm *dimm, enum ndctl_key_type key_type) { char desc[ND_KEY_DESC_SIZE]; int rc; rc = get_key_desc(dimm, desc, key_type); if (rc < 0) return rc; return keyctl_search(KEY_SPEC_USER_KEYRING, "encrypted", desc, 0); } static key_serial_t dimm_create_key(struct ndctl_dimm *dimm, const char *kek, enum ndctl_key_type key_type) { char desc[ND_KEY_DESC_SIZE]; char path[PATH_MAX]; char cmd[ND_KEY_CMD_SIZE]; key_serial_t key; void *buffer; int rc; ssize_t size; FILE *fp; ssize_t wrote; struct stat st; if (ndctl_dimm_is_active(dimm)) { fprintf(stderr, "regions active on %s, op failed\n", ndctl_dimm_get_devname(dimm)); return -EBUSY; } rc = get_key_desc(dimm, desc, key_type); if (rc < 0) return rc; /* make sure it's not already in the key ring */ key = keyctl_search(KEY_SPEC_USER_KEYRING, "encrypted", desc, 0); if (key > 0) { fprintf(stderr, "Error: key already present in user keyring\n"); return -EEXIST; } rc = get_key_path(dimm, path, key_type); if (rc < 0) return rc; rc = stat(path, &st); if (rc == 0) { fprintf(stderr, "%s already exists!\n", path); return -EEXIST; } rc = sprintf(cmd, "new enc32 %s 32", kek); if (rc < 0) { fprintf(stderr, "sprintf: %s\n", strerror(errno)); return -errno; } key = add_key("encrypted", desc, cmd, strlen(cmd), KEY_SPEC_USER_KEYRING); if (key < 0) { fprintf(stderr, "add_key failed: %s\n", strerror(errno)); return -errno; } size = keyctl_read_alloc(key, &buffer); if (size < 0) { fprintf(stderr, "keyctl_read_alloc failed: %s\n", strerror(errno)); keyctl_unlink(key, KEY_SPEC_USER_KEYRING); return rc; } fp = fopen(path, "w"); if (!fp) { rc = -errno; fprintf(stderr, "Unable to open file %s: %s\n", path, strerror(errno)); free(buffer); return rc; } wrote = fwrite(buffer, 1, size, fp); if (wrote != size) { if (wrote == -1) rc = -errno; else rc = -EIO; fprintf(stderr, "Failed to write to %s: %s\n", path, strerror(-rc)); fclose(fp); free(buffer); return rc; } fclose(fp); free(buffer); return key; } static key_serial_t dimm_load_key(struct ndctl_dimm *dimm, enum ndctl_key_type key_type) { key_serial_t key; char desc[ND_KEY_DESC_SIZE]; char path[PATH_MAX]; int rc; char *blob; int size; if (ndctl_dimm_is_active(dimm)) { fprintf(stderr, "regions active on %s, op failed\n", ndctl_dimm_get_devname(dimm)); return -EBUSY; } rc = get_key_desc(dimm, desc, key_type); if (rc < 0) return rc; rc = get_key_path(dimm, path, key_type); if (rc < 0) return rc; blob = ndctl_load_key_blob(path, &size, NULL, -1, KEY_ENCRYPTED); if (!blob) return -ENOMEM; key = add_key("encrypted", desc, blob, size, KEY_SPEC_USER_KEYRING); free(blob); if (key < 0) { fprintf(stderr, "add_key failed: %s\n", strerror(errno)); return -errno; } return key; } /* * The function will check to see if the existing key is there and remove * from user key ring if it is. Rename the existing key blob to old key * blob, and then attempt to inject the key as old key into the user key * ring. */ static key_serial_t move_key_to_old(struct ndctl_dimm *dimm, enum ndctl_key_type key_type) { int rc; key_serial_t key; char old_path[PATH_MAX]; char new_path[PATH_MAX]; enum ndctl_key_type okey_type; if (ndctl_dimm_is_active(dimm)) { fprintf(stderr, "regions active on %s, op failed\n", ndctl_dimm_get_devname(dimm)); return -EBUSY; } key = dimm_check_key(dimm, key_type); if (key > 0) keyctl_unlink(key, KEY_SPEC_USER_KEYRING); if (key_type == ND_USER_KEY) okey_type = ND_USER_OLD_KEY; else if (key_type == ND_MASTER_KEY) okey_type = ND_MASTER_OLD_KEY; else return -EINVAL; rc = get_key_path(dimm, old_path, key_type); if (rc < 0) return rc; rc = get_key_path(dimm, new_path, okey_type); if (rc < 0) return rc; rc = rename(old_path, new_path); if (rc < 0) { fprintf(stderr, "rename failed from %s to %s: %s\n", old_path, new_path, strerror(errno)); return -errno; } return dimm_load_key(dimm, okey_type); } static int dimm_remove_key(struct ndctl_dimm *dimm, enum ndctl_key_type key_type) { key_serial_t key; char path[PATH_MAX]; int rc; key = dimm_check_key(dimm, key_type); if (key > 0) keyctl_unlink(key, KEY_SPEC_USER_KEYRING); rc = get_key_path(dimm, path, key_type); if (rc < 0) return rc; rc = unlink(path); if (rc < 0) { fprintf(stderr, "delete file %s failed: %s\n", path, strerror(errno)); return -errno; } return 0; } static int verify_kek(struct ndctl_dimm *dimm, const char *kek) { char *type, *desc, *key_handle; key_serial_t key; int rc = 0; key_handle = strdup(kek); if (!key_handle) return -ENOMEM; type = strtok(key_handle, ":"); if (!type) { fprintf(stderr, "No key type found for kek handle\n"); rc = -EINVAL; goto out; } if (strcmp(type, "trusted") != 0 && strcmp(type, "user") != 0) { fprintf(stderr, "No such key type: %s", type); rc = -EINVAL; goto out; } desc = strtok(NULL, ":"); if (!desc) { fprintf(stderr, "No description found for kek handle\n"); rc = -EINVAL; goto out; } key = keyctl_search(KEY_SPEC_USER_KEYRING, type, desc, 0); if (key < 0) { fprintf(stderr, "No key encryption key found\n"); rc = key; goto out; } out: free(key_handle); return rc; } int ndctl_dimm_setup_key(struct ndctl_dimm *dimm, const char *kek, enum ndctl_key_type key_type) { key_serial_t key; int rc; rc = verify_kek(dimm, kek); if (rc < 0) return rc; key = dimm_create_key(dimm, kek, key_type); if (key < 0) return key; if (key_type == ND_MASTER_KEY) rc = ndctl_dimm_update_master_passphrase(dimm, 0, key); else rc = ndctl_dimm_update_passphrase(dimm, 0, key); if (rc < 0) { dimm_remove_key(dimm, key_type); return rc; } return 0; } static char *get_current_kek(struct ndctl_dimm *dimm, enum ndctl_key_type key_type) { key_serial_t key; char *key_buf; long rc; char *type, *desc; key = dimm_check_key(dimm, key_type); if (key < 0) return NULL; rc = keyctl_read_alloc(key, (void **)&key_buf); if (rc < 0) return NULL; rc = sscanf(key_buf, "%ms %ms", &type, &desc); if (rc < 0) return NULL; free(key_buf); free(type); return desc; } int ndctl_dimm_update_key(struct ndctl_dimm *dimm, const char *kek, enum ndctl_key_type key_type) { int rc; key_serial_t old_key, new_key; char *current_kek = NULL; enum ndctl_key_type okey_type; if (kek) { rc = verify_kek(dimm, kek); if (rc < 0) return rc; } else { /* find current kek */ current_kek = get_current_kek(dimm, key_type); if (!current_kek) return -ENOKEY; } if (key_type == ND_USER_KEY) okey_type = ND_USER_OLD_KEY; else if (key_type == ND_MASTER_KEY) okey_type = ND_MASTER_OLD_KEY; else return -EINVAL; /* * 1. check if current key is loaded and remove * 2. move current key blob to old key blob * 3. load old key blob * 4. trigger change key with old and new key * 5. remove old key * 6. remove old key blob */ old_key = move_key_to_old(dimm, key_type); if (old_key < 0) return old_key; new_key = dimm_create_key(dimm, current_kek ? current_kek : kek, key_type); free(current_kek); /* need to create new key here */ if (new_key < 0) { new_key = dimm_load_key(dimm, key_type); if (new_key < 0) return new_key; } if (key_type == ND_MASTER_KEY) rc = ndctl_dimm_update_master_passphrase(dimm, old_key, new_key); else rc = ndctl_dimm_update_passphrase(dimm, old_key, new_key); if (rc < 0) return rc; rc = dimm_remove_key(dimm, okey_type); if (rc < 0) return rc; return 0; } static key_serial_t check_dimm_key(struct ndctl_dimm *dimm, bool need_key, enum ndctl_key_type key_type) { key_serial_t key; key = dimm_check_key(dimm, key_type); if (key < 0) { key = dimm_load_key(dimm, key_type); if (key < 0 && need_key) { fprintf(stderr, "Unable to load key\n"); return -ENOKEY; } else key = 0; } return key; } static int run_key_op(struct ndctl_dimm *dimm, key_serial_t key, int (*run_op)(struct ndctl_dimm *, long), const char *name) { int rc; rc = run_op(dimm, key); if (rc < 0) { fprintf(stderr, "Failed %s for %s\n", name, ndctl_dimm_get_devname(dimm)); return rc; } return 0; } static int discard_key(struct ndctl_dimm *dimm, enum ndctl_key_type key_type) { int rc; rc = dimm_remove_key(dimm, key_type); if (rc < 0) { fprintf(stderr, "Unable to cleanup key.\n"); return rc; } return 0; } int ndctl_dimm_remove_key(struct ndctl_dimm *dimm, enum ndctl_key_type key_type) { key_serial_t key; int rc; key = check_dimm_key(dimm, true, key_type); if (key < 0) return key; if (key_type == ND_MASTER_KEY) rc = run_key_op(dimm, key, ndctl_dimm_disable_master_passphrase, "remove master passphrase"); else rc = run_key_op(dimm, key, ndctl_dimm_disable_passphrase, "remove passphrase"); if (rc < 0) return rc; return discard_key(dimm, key_type); } int ndctl_dimm_secure_erase_key(struct ndctl_dimm *dimm, enum ndctl_key_type key_type) { key_serial_t key = 0; int rc; if (key_type != ND_ZERO_KEY) { key = check_dimm_key(dimm, true, key_type); if (key < 0) return key; } if (key_type == ND_MASTER_KEY) rc = run_key_op(dimm, key, ndctl_dimm_master_secure_erase, "master crypto erase"); else if (key_type == ND_USER_KEY || key_type == ND_ZERO_KEY) rc = run_key_op(dimm, key, ndctl_dimm_secure_erase, "crypto erase"); else rc = -EINVAL; if (rc < 0) return rc; if (key_type == ND_USER_KEY) return discard_key(dimm, key_type); return 0; } int ndctl_dimm_overwrite_key(struct ndctl_dimm *dimm) { key_serial_t key; int rc; key = check_dimm_key(dimm, false, ND_USER_KEY); if (key < 0) return key; rc = run_key_op(dimm, key, ndctl_dimm_overwrite, "overwrite"); if (rc < 0) return rc; return 0; } ndctl-81/ndctl/keys.h000066400000000000000000000032201476737544500146630ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (C) 2019-2020 Intel Corporation. All rights reserved. */ #ifndef _NDCTL_UTIL_KEYS_H_ #define _NDCTL_UTIL_KEYS_H_ enum ndctl_key_type { ND_USER_KEY, ND_USER_OLD_KEY, ND_MASTER_KEY, ND_MASTER_OLD_KEY, ND_ZERO_KEY, }; enum key_type { KEY_USER = 0, KEY_TRUSTED, KEY_ENCRYPTED, }; #ifdef ENABLE_KEYUTILS char *ndctl_load_key_blob(const char *path, int *size, const char *postfix, int dirfd, enum key_type key_type); int ndctl_dimm_setup_key(struct ndctl_dimm *dimm, const char *kek, enum ndctl_key_type key_type); int ndctl_dimm_update_key(struct ndctl_dimm *dimm, const char *kek, enum ndctl_key_type key_type); int ndctl_dimm_remove_key(struct ndctl_dimm *dimm, enum ndctl_key_type key_type); int ndctl_dimm_secure_erase_key(struct ndctl_dimm *dimm, enum ndctl_key_type key_type); int ndctl_dimm_overwrite_key(struct ndctl_dimm *dimm); #else char *ndctl_load_key_blob(const char *path, int *size, const char *postfix, int dirfd, enum key_type key_type) { return NULL; } static inline int ndctl_dimm_setup_key(struct ndctl_dimm *dimm, const char *kek, enum ndctl_key_type key_type) { return -EOPNOTSUPP; } static inline int ndctl_dimm_update_key(struct ndctl_dimm *dimm, const char *kek, enum ndctl_key_type key_type) { return -EOPNOTSUPP; } static inline int ndctl_dimm_remove_key(struct ndctl_dimm *dimm) { return -EOPNOTSUPP; } static inline int ndctl_dimm_secure_erase_key(struct ndctl_dimm *dimm, enum ndctl_key_type key_type) { return -EOPNOTSUPP; } static inline int ndctl_dimm_overwrite_key(struct ndctl_dimm *dimm) { return -EOPNOTSUPP; } #endif /* ENABLE_KEYUTILS */ #endif ndctl-81/ndctl/keys.readme000066400000000000000000000000441476737544500156720ustar00rootroot00000000000000See 'ndctl setup-passphrase --help' ndctl-81/ndctl/lib/000077500000000000000000000000001476737544500143105ustar00rootroot00000000000000ndctl-81/ndctl/lib/.gitignore000066400000000000000000000000701476737544500162750ustar00rootroot00000000000000.dirstamp .deps/ .libs/ *.la *.lo libabc.pc test-libabc ndctl-81/ndctl/lib/ars.c000066400000000000000000000215031476737544500152420ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-2.1 // Copyright (C) 2014-2020, Intel Corporation. All rights reserved. #include #include #include #include "private.h" static u32 get_ars_command_status(struct ndctl_cmd *cmd) { switch (cmd->type) { case ND_CMD_ARS_CAP: return cmd->ars_cap->status; case ND_CMD_ARS_START: return cmd->ars_start->status; case ND_CMD_ARS_STATUS: return cmd->ars_status->status; case ND_CMD_CLEAR_ERROR: return cmd->clear_err->status; } return -1U; } NDCTL_EXPORT struct ndctl_cmd *ndctl_bus_cmd_new_ars_cap(struct ndctl_bus *bus, unsigned long long address, unsigned long long len) { struct ndctl_ctx *ctx = ndctl_bus_get_ctx(bus); struct ndctl_cmd *cmd; size_t size; if (!ndctl_bus_is_cmd_supported(bus, ND_CMD_ARS_CAP)) { dbg(ctx, "unsupported cmd\n"); return NULL; } size = sizeof(*cmd) + sizeof(struct nd_cmd_ars_cap); cmd = calloc(1, size); if (!cmd) return NULL; cmd->bus = bus; ndctl_cmd_ref(cmd); cmd->type = ND_CMD_ARS_CAP; cmd->get_firmware_status = get_ars_command_status; cmd->size = size; cmd->status = 1; cmd->ars_cap->address = address; cmd->ars_cap->length = len; return cmd; } static bool validate_clear_error(struct ndctl_cmd *ars_cap) { if (!is_power_of_2(ars_cap->ars_cap->clear_err_unit)) return false; return true; } static bool __validate_ars_cap(struct ndctl_cmd *ars_cap) { if (ars_cap->type != ND_CMD_ARS_CAP || ars_cap->status != 0) return false; if ((ars_cap->get_firmware_status(ars_cap) & ARS_STATUS_MASK) != 0) return false; return validate_clear_error(ars_cap); } #define validate_ars_cap(ctx, ars_cap) \ ({ \ bool __valid = __validate_ars_cap(ars_cap); \ if (!__valid) \ dbg(ctx, "expected sucessfully completed ars_cap command\n"); \ __valid; \ }) NDCTL_EXPORT struct ndctl_cmd *ndctl_bus_cmd_new_ars_start(struct ndctl_cmd *ars_cap, int type) { struct ndctl_bus *bus = ars_cap->bus; struct ndctl_ctx *ctx = ndctl_bus_get_ctx(bus); struct ndctl_cmd *cmd; size_t size; if (!ndctl_bus_is_cmd_supported(bus, ND_CMD_ARS_START)) { dbg(ctx, "unsupported cmd\n"); return NULL; } if (!validate_ars_cap(ctx, ars_cap)) return NULL; if (!(ars_cap->get_firmware_status(ars_cap) >> ARS_EXT_STATUS_SHIFT & type)) { dbg(ctx, "ars_cap does not show requested type as supported\n"); return NULL; } size = sizeof(*cmd) + sizeof(struct nd_cmd_ars_start); cmd = calloc(1, size); if (!cmd) return NULL; cmd->bus = bus; ndctl_cmd_ref(cmd); cmd->type = ND_CMD_ARS_START; cmd->get_firmware_status = get_ars_command_status; cmd->size = size; cmd->status = 1; cmd->ars_start->address = ars_cap->ars_cap->address; cmd->ars_start->length = ars_cap->ars_cap->length; cmd->ars_start->type = type; return cmd; } NDCTL_EXPORT struct ndctl_cmd *ndctl_bus_cmd_new_ars_status(struct ndctl_cmd *ars_cap) { struct nd_cmd_ars_cap *ars_cap_cmd = ars_cap->ars_cap; struct ndctl_bus *bus = ars_cap->bus; struct ndctl_ctx *ctx = ndctl_bus_get_ctx(bus); struct ndctl_cmd *cmd; size_t size; if (!ndctl_bus_is_cmd_supported(bus, ND_CMD_ARS_STATUS)) { dbg(ctx, "unsupported cmd\n"); return NULL; } if (!validate_ars_cap(ctx, ars_cap)) return NULL; if (ars_cap_cmd->max_ars_out == 0) { dbg(ctx, "invalid ars_cap\n"); return NULL; } size = sizeof(*cmd) + ars_cap_cmd->max_ars_out; /* * Older kernels have a bug that miscalculates the output length of the * ars status and will overrun the provided buffer by 4 bytes, * corrupting the memory. Add an additional 4 bytes in the allocation * size to prevent that corruption. See kernel patch for more details: * * https://patchwork.kernel.org/patch/10563103/ */ cmd = calloc(1, size + 4); if (!cmd) return NULL; cmd->bus = bus; ndctl_cmd_ref(cmd); cmd->type = ND_CMD_ARS_STATUS; cmd->get_firmware_status = get_ars_command_status; cmd->size = size; cmd->status = 1; cmd->ars_status->out_length = ars_cap_cmd->max_ars_out; return cmd; } NDCTL_EXPORT unsigned int ndctl_cmd_ars_cap_get_size(struct ndctl_cmd *ars_cap) { struct ndctl_ctx *ctx = ndctl_bus_get_ctx(cmd_to_bus(ars_cap)); if (ars_cap->type == ND_CMD_ARS_CAP && ars_cap->status == 0) { dbg(ctx, "max_ars_out: %d\n", ars_cap->ars_cap->max_ars_out); return ars_cap->ars_cap->max_ars_out; } dbg(ctx, "invalid ars_cap\n"); return 0; } NDCTL_EXPORT int ndctl_cmd_ars_cap_get_range(struct ndctl_cmd *ars_cap, struct ndctl_range *range) { struct ndctl_ctx *ctx = ndctl_bus_get_ctx(cmd_to_bus(ars_cap)); if (range && ars_cap->type == ND_CMD_ARS_CAP && ars_cap->status == 0) { struct nd_cmd_ars_cap *cap = ars_cap->ars_cap; dbg(ctx, "range: %llx - %llx\n", cap->address, cap->length); range->address = cap->address; range->length = cap->length; return 0; } dbg(ctx, "invalid ars_cap\n"); return -EINVAL; } NDCTL_EXPORT unsigned int ndctl_cmd_ars_cap_get_clear_unit( struct ndctl_cmd *ars_cap) { struct ndctl_ctx *ctx = ndctl_bus_get_ctx(cmd_to_bus(ars_cap)); if (ars_cap->type == ND_CMD_ARS_CAP && ars_cap->status == 0) { dbg(ctx, "clear_err_unit: %d\n", ars_cap->ars_cap->clear_err_unit); return ars_cap->ars_cap->clear_err_unit; } dbg(ctx, "invalid ars_cap\n"); return 0; } static bool __validate_ars_stat(struct ndctl_cmd *ars_stat) { /* * A positive status indicates an underrun, but that is fine since * the firmware is not expected to completely fill the max_ars_out * sized buffer. */ if (ars_stat->type != ND_CMD_ARS_STATUS || ars_stat->status < 0) return false; if ((ndctl_cmd_get_firmware_status(ars_stat) & ARS_STATUS_MASK) != 0) return false; return true; } #define validate_ars_stat(ctx, ars_stat) \ ({ \ bool __valid = __validate_ars_stat(ars_stat); \ if (!__valid) \ dbg(ctx, "expected sucessfully completed ars_stat command\n"); \ __valid; \ }) NDCTL_EXPORT int ndctl_cmd_ars_in_progress(struct ndctl_cmd *cmd) { struct ndctl_ctx *ctx = ndctl_bus_get_ctx(cmd_to_bus(cmd)); if (!validate_ars_stat(ctx, cmd)) return 0; return (ndctl_cmd_get_firmware_status(cmd) == 1 << 16); } NDCTL_EXPORT unsigned int ndctl_cmd_ars_num_records(struct ndctl_cmd *ars_stat) { struct ndctl_ctx *ctx = ndctl_bus_get_ctx(cmd_to_bus(ars_stat)); if (!validate_ars_stat(ctx, ars_stat)) return 0; return ars_stat->ars_status->num_records; } NDCTL_EXPORT unsigned long long ndctl_cmd_ars_get_record_addr( struct ndctl_cmd *ars_stat, unsigned int rec_index) { struct ndctl_ctx *ctx = ndctl_bus_get_ctx(cmd_to_bus(ars_stat)); if (!validate_ars_stat(ctx, ars_stat)) return 0; if (rec_index >= ars_stat->ars_status->num_records) { dbg(ctx, "invalid record index\n"); return 0; } return ars_stat->ars_status->records[rec_index].err_address; } NDCTL_EXPORT unsigned long long ndctl_cmd_ars_get_record_len( struct ndctl_cmd *ars_stat, unsigned int rec_index) { struct ndctl_ctx *ctx = ndctl_bus_get_ctx(cmd_to_bus(ars_stat)); if (!validate_ars_stat(ctx, ars_stat)) return 0; if (rec_index >= ars_stat->ars_status->num_records) { dbg(ctx, "invalid record index\n"); return 0; } return ars_stat->ars_status->records[rec_index].length; } NDCTL_EXPORT int ndctl_cmd_ars_stat_get_flag_overflow( struct ndctl_cmd *ars_stat) { struct ndctl_ctx *ctx = ndctl_bus_get_ctx(cmd_to_bus(ars_stat)); if (!validate_ars_stat(ctx, ars_stat)) return -EINVAL; return !!(ars_stat->ars_status->flags & ND_ARS_STAT_FLAG_OVERFLOW); } NDCTL_EXPORT struct ndctl_cmd *ndctl_bus_cmd_new_clear_error( unsigned long long address, unsigned long long len, struct ndctl_cmd *ars_cap) { size_t size; unsigned int mask; struct nd_cmd_ars_cap *cap; struct ndctl_cmd *clear_err; struct ndctl_bus *bus = ars_cap->bus; struct ndctl_ctx *ctx = ndctl_bus_get_ctx(bus); if (!ndctl_bus_is_cmd_supported(bus, ND_CMD_ARS_STATUS)) { dbg(ctx, "unsupported cmd\n"); return NULL; } if (!validate_ars_cap(ctx, ars_cap)) return NULL; cap = ars_cap->ars_cap; if (address < cap->address || address > (cap->address + cap->length) || address + len > (cap->address + cap->length)) { dbg(ctx, "request out of range (relative to ars_cap)\n"); return NULL; } mask = cap->clear_err_unit - 1; if ((address | len) & mask) { dbg(ctx, "request unaligned\n"); return NULL; } size = sizeof(*clear_err) + sizeof(struct nd_cmd_clear_error); clear_err = calloc(1, size); if (!clear_err) return NULL; ndctl_cmd_ref(clear_err); clear_err->bus = bus; clear_err->type = ND_CMD_CLEAR_ERROR; clear_err->get_firmware_status = get_ars_command_status; clear_err->size = size; clear_err->status = 1; clear_err->clear_err->address = address; clear_err->clear_err->length = len; return clear_err; } NDCTL_EXPORT unsigned long long ndctl_cmd_clear_error_get_cleared( struct ndctl_cmd *clear_err) { struct ndctl_ctx *ctx = ndctl_bus_get_ctx(cmd_to_bus(clear_err)); if (clear_err->type == ND_CMD_CLEAR_ERROR && clear_err->status == 0) return clear_err->clear_err->cleared; dbg(ctx, "invalid clear_err\n"); return 0; } ndctl-81/ndctl/lib/dimm.c000066400000000000000000000527561476737544500154210ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-2.1 // Copyright (C) 2014-2020, Intel Corporation. All rights reserved. #include #include #include #include #include #include #include #include "private.h" static const char NSINDEX_SIGNATURE[] = "NAMESPACE_INDEX\0"; /* * Note, best_seq(), inc_seq(), sizeof_namespace_index() * nvdimm_num_label_slots(), label_validate(), and label_write_index() * are copied from drivers/nvdimm/label.c in the Linux kernel with the * following modifications: * 1/ s,nd_,,gc * 2/ s,ndd->nsarea.config_size,ndd->config_size,gc * 3/ s,dev_dbg(dev,dbg(ctx,gc * 4/ s,__le,le,gc * 5/ s,__cpu_to,cpu_to,gc * 6/ remove flags argument to label_write_index * 7/ dropped clear_bit_le() usage in label_write_index * 8/ s,nvdimm_drvdata,nvdimm_data,gc */ static unsigned inc_seq(unsigned seq) { static const unsigned next[] = { 0, 2, 3, 1 }; return next[seq & 3]; } static u32 best_seq(u32 a, u32 b) { a &= NSINDEX_SEQ_MASK; b &= NSINDEX_SEQ_MASK; if (a == 0 || a == b) return b; else if (b == 0) return a; else if (inc_seq(a) == b) return b; else return a; } static struct ndctl_dimm *to_dimm(struct nvdimm_data *ndd) { return container_of(ndd, struct ndctl_dimm, ndd); } static unsigned int sizeof_namespace_label(struct nvdimm_data *ndd) { return ndctl_dimm_sizeof_namespace_label(to_dimm(ndd)); } static size_t __sizeof_namespace_index(u32 nslot) { return ALIGN(sizeof(struct namespace_index) + DIV_ROUND_UP(nslot, 8), NSINDEX_ALIGN); } static int __nvdimm_num_label_slots(struct nvdimm_data *ndd, size_t index_size) { return (ndd->config_size - index_size * 2) / sizeof_namespace_label(ndd); } static int nvdimm_num_label_slots(struct nvdimm_data *ndd) { u32 tmp_nslot, n; tmp_nslot = ndd->config_size / sizeof_namespace_label(ndd); n = __sizeof_namespace_index(tmp_nslot) / NSINDEX_ALIGN; return __nvdimm_num_label_slots(ndd, NSINDEX_ALIGN * n); } static unsigned int sizeof_namespace_index(struct nvdimm_data *ndd) { u32 nslot, space, size; struct ndctl_dimm *dimm = to_dimm(ndd); struct ndctl_ctx *ctx = ndctl_dimm_get_ctx(dimm); /* * Per UEFI 2.7, the minimum size of the Label Storage Area is * large enough to hold 2 index blocks and 2 labels. The * minimum index block size is 256 bytes, and the minimum label * size is 256 bytes. */ nslot = nvdimm_num_label_slots(ndd); space = ndd->config_size - nslot * sizeof_namespace_label(ndd); size = __sizeof_namespace_index(nslot) * 2; if (size <= space && nslot >= 2) return size / 2; err(ctx, "%s: label area (%ld) too small to host (%d byte) labels\n", ndctl_dimm_get_devname(dimm), ndd->config_size, sizeof_namespace_label(ndd)); return 0; } static struct namespace_index *to_namespace_index(struct nvdimm_data *ndd, int i) { char *index; if (i < 0) return NULL; index = (char *) ndd->data + sizeof_namespace_index(ndd) * i; return (struct namespace_index *) index; } static int __label_validate(struct nvdimm_data *ndd) { struct ndctl_ctx *ctx = ndctl_dimm_get_ctx(to_dimm(ndd)); /* * On media label format consists of two index blocks followed * by an array of labels. None of these structures are ever * updated in place. A sequence number tracks the current * active index and the next one to write, while labels are * written to free slots. * * +------------+ * | | * | nsindex0 | * | | * +------------+ * | | * | nsindex1 | * | | * +------------+ * | label0 | * +------------+ * | label1 | * +------------+ * | | * ....nslot... * | | * +------------+ * | labelN | * +------------+ */ struct namespace_index *nsindex[] = { to_namespace_index(ndd, 0), to_namespace_index(ndd, 1), }; const int num_index = ARRAY_SIZE(nsindex); bool valid[2] = { 0 }; int i, num_valid = 0; u32 seq; for (i = 0; i < num_index; i++) { u32 nslot; u8 sig[NSINDEX_SIG_LEN]; u64 sum_save, sum, size; unsigned int version, labelsize; memcpy(sig, nsindex[i]->sig, NSINDEX_SIG_LEN); if (memcmp(sig, NSINDEX_SIGNATURE, NSINDEX_SIG_LEN) != 0) { dbg(ctx, "nsindex%d signature invalid\n", i); continue; } /* label sizes larger than 128 arrived with v1.2 */ version = le16_to_cpu(nsindex[i]->major) * 100 + le16_to_cpu(nsindex[i]->minor); if (version >= 102) labelsize = 1 << (7 + nsindex[i]->labelsize); else labelsize = 128; if (labelsize != sizeof_namespace_label(ndd)) { dbg(ctx, "nsindex%d labelsize %d invalid\n", i, nsindex[i]->labelsize); continue; } sum_save = le64_to_cpu(nsindex[i]->checksum); nsindex[i]->checksum = cpu_to_le64(0); sum = fletcher64(nsindex[i], sizeof_namespace_index(ndd), 1); nsindex[i]->checksum = cpu_to_le64(sum_save); if (sum != sum_save) { dbg(ctx, "nsindex%d checksum invalid\n", i); continue; } seq = le32_to_cpu(nsindex[i]->seq); if ((seq & NSINDEX_SEQ_MASK) == 0) { dbg(ctx, "nsindex%d sequence: %#x invalid\n", i, seq); continue; } /* sanity check the index against expected values */ if (le64_to_cpu(nsindex[i]->myoff) != i * sizeof_namespace_index(ndd)) { dbg(ctx, "nsindex%d myoff: %#llx invalid\n", i, (unsigned long long) le64_to_cpu(nsindex[i]->myoff)); continue; } if (le64_to_cpu(nsindex[i]->otheroff) != (!i) * sizeof_namespace_index(ndd)) { dbg(ctx, "nsindex%d otheroff: %#llx invalid\n", i, (unsigned long long) le64_to_cpu(nsindex[i]->otheroff)); continue; } size = le64_to_cpu(nsindex[i]->mysize); if (size > sizeof_namespace_index(ndd) || size < sizeof(struct namespace_index)) { dbg(ctx, "nsindex%d mysize: %#zx invalid\n", i, size); continue; } nslot = le32_to_cpu(nsindex[i]->nslot); if (nslot * sizeof_namespace_label(ndd) + 2 * sizeof_namespace_index(ndd) > ndd->config_size) { dbg(ctx, "nsindex%d nslot: %u invalid, config_size: %#zx\n", i, nslot, ndd->config_size); continue; } valid[i] = true; num_valid++; } switch (num_valid) { case 0: break; case 1: for (i = 0; i < num_index; i++) if (valid[i]) return i; /* can't have num_valid > 0 but valid[] = { false, false } */ err(ctx, "unexpected index-block parse error\n"); break; default: /* pick the best index... */ seq = best_seq(le32_to_cpu(nsindex[0]->seq), le32_to_cpu(nsindex[1]->seq)); if (seq == (le32_to_cpu(nsindex[1]->seq) & NSINDEX_SEQ_MASK)) return 1; else return 0; break; } return -EINVAL; } NDCTL_EXPORT unsigned int ndctl_dimm_sizeof_namespace_index(struct ndctl_dimm *dimm) { return sizeof_namespace_index(&dimm->ndd); } /* * If the dimm labels have not been previously validated this routine * will make up a default size. Otherwise, it will pick the size based * on what version is specified in the index block. */ NDCTL_EXPORT unsigned int ndctl_dimm_sizeof_namespace_label(struct ndctl_dimm *dimm) { struct nvdimm_data *ndd = &dimm->ndd; struct namespace_index nsindex; ssize_t offset, size; int v1 = 0, v2 = 0; if (ndd->nslabel_size) return ndd->nslabel_size; for (offset = 0; offset < NSINDEX_ALIGN * 2; offset += NSINDEX_ALIGN) { ssize_t len = (ssize_t) sizeof(nsindex); len = ndctl_cmd_cfg_read_get_data(ndd->cmd_read, &nsindex, len, offset); if (len < 0) break; /* * Since we're doing a best effort parsing we don't * fully validate the index block. Instead just assume * v1.1 unless there's 2 index blocks that say v1.2. */ if (le16_to_cpu(nsindex.major) == 1) { if (le16_to_cpu(nsindex.minor) == 1) v1++; else if (le16_to_cpu(nsindex.minor) == 2) v2++; } } if (v2 > v1) size = 256; else size = 128; ndd->nslabel_size = size; return size; } static int label_validate(struct nvdimm_data *ndd) { /* * In order to probe for and validate namespace index blocks we * need to know the size of the labels, and we can't trust the * size of the labels until we validate the index blocks. * Resolve this dependency loop by probing for known label * sizes, but default to v1.2 256-byte namespace labels if * discovery fails. */ int label_size[] = { 128, 256 }; int i, rc; for (i = 0; (size_t) i < ARRAY_SIZE(label_size); i++) { ndd->nslabel_size = label_size[i]; rc = __label_validate(ndd); if (rc >= 0) return nvdimm_num_label_slots(ndd); } return -EINVAL; } static int nvdimm_set_config_data(struct nvdimm_data *ndd, size_t offset, void *buf, size_t len) { struct ndctl_cmd *cmd_write; int rc; cmd_write = ndctl_dimm_cmd_new_cfg_write(ndd->cmd_read); if (!cmd_write) return -ENXIO; rc = ndctl_cmd_cfg_write_set_data(cmd_write, buf, len, offset); if (rc < 0) goto out; rc = ndctl_cmd_submit_xlat(cmd_write); if (rc < 0) rc = -ENXIO; out: ndctl_cmd_unref(cmd_write); return rc; } static int label_next_nsindex(int index) { if (index < 0) return -1; return (index + 1) % 2; } static struct namespace_label *label_base(struct nvdimm_data *ndd) { char *base = (char *) to_namespace_index(ndd, 0); base += 2 * sizeof_namespace_index(ndd); return (struct namespace_label *) base; } static void init_ndd(struct nvdimm_data *ndd, struct ndctl_cmd *cmd_read, struct ndctl_cmd *cmd_size) { ndctl_cmd_unref(ndd->cmd_read); memset(ndd, 0, sizeof(*ndd)); ndd->cmd_read = cmd_read; ndctl_cmd_ref(cmd_read); ndd->data = cmd_read->iter.total_buf; ndd->config_size = cmd_size->get_size->config_size; ndd->ns_current = -1; ndd->ns_next = -1; } static int write_label_index(struct ndctl_dimm *dimm, enum ndctl_namespace_version ver, unsigned index, unsigned seq) { struct nvdimm_data *ndd = &dimm->ndd; struct namespace_index *nsindex; unsigned long offset; u64 checksum; u32 nslot; /* * We may have initialized ndd to whatever labelsize is * currently on the dimm during label_validate(), so we reset it * to the desired version here. */ switch (ver) { case NDCTL_NS_VERSION_1_1: ndd->nslabel_size = 128; break; case NDCTL_NS_VERSION_1_2: ndd->nslabel_size = 256; break; default: return -EINVAL; } nsindex = to_namespace_index(ndd, index); nslot = nvdimm_num_label_slots(ndd); memcpy(nsindex->sig, NSINDEX_SIGNATURE, NSINDEX_SIG_LEN); memset(nsindex->flags, 0, 3); nsindex->labelsize = sizeof_namespace_label(ndd) >> 8; nsindex->seq = cpu_to_le32(seq); offset = (unsigned long) nsindex - (unsigned long) to_namespace_index(ndd, 0); nsindex->myoff = cpu_to_le64(offset); nsindex->mysize = cpu_to_le64(sizeof_namespace_index(ndd)); offset = (unsigned long) to_namespace_index(ndd, label_next_nsindex(index)) - (unsigned long) to_namespace_index(ndd, 0); nsindex->otheroff = cpu_to_le64(offset); offset = (unsigned long) label_base(ndd) - (unsigned long) to_namespace_index(ndd, 0); nsindex->labeloff = cpu_to_le64(offset); nsindex->nslot = cpu_to_le32(nslot); nsindex->major = cpu_to_le16(1); if (sizeof_namespace_label(ndd) < 256) nsindex->minor = cpu_to_le16(1); else nsindex->minor = cpu_to_le16(2); nsindex->checksum = cpu_to_le64(0); /* init label bitmap */ memset(nsindex->free, 0xff, ALIGN(nslot, BITS_PER_LONG) / 8); checksum = fletcher64(nsindex, sizeof_namespace_index(ndd), 1); nsindex->checksum = cpu_to_le64(checksum); return nvdimm_set_config_data(ndd, le64_to_cpu(nsindex->myoff), nsindex, sizeof_namespace_index(ndd)); } NDCTL_EXPORT int ndctl_dimm_init_labels(struct ndctl_dimm *dimm, enum ndctl_namespace_version v) { struct ndctl_bus *bus = ndctl_dimm_get_bus(dimm); struct ndctl_ctx *ctx = ndctl_dimm_get_ctx(dimm); struct nvdimm_data *ndd = &dimm->ndd; struct ndctl_region *region; int i; if (!ndd->cmd_read) { err(ctx, "%s: needs to be initialized by ndctl_dimm_read_labels\n", ndctl_dimm_get_devname(dimm)); return -EINVAL; } ndctl_region_foreach(bus, region) { struct ndctl_dimm *match; ndctl_dimm_foreach_in_region(region, match) if (match == dimm) { region_flag_refresh(region); break; } } for (i = 0; i < 2; i++) { int rc; rc = write_label_index(dimm, v, i, 3 - i); if (rc < 0) return rc; } return nvdimm_num_label_slots(ndd); } NDCTL_EXPORT int ndctl_dimm_validate_labels(struct ndctl_dimm *dimm) { struct ndctl_ctx *ctx = ndctl_dimm_get_ctx(dimm); struct nvdimm_data *ndd = &dimm->ndd; if (!ndd->cmd_read) { err(ctx, "%s: needs to be initialized by ndctl_dimm_read_labels\n", ndctl_dimm_get_devname(dimm)); return -EINVAL; } return label_validate(&dimm->ndd); } NDCTL_EXPORT struct ndctl_cmd *ndctl_dimm_read_label_index(struct ndctl_dimm *dimm) { struct ndctl_bus *bus = ndctl_dimm_get_bus(dimm); struct ndctl_cmd *cmd_size, *cmd_read; struct nvdimm_data *ndd = &dimm->ndd; int rc; rc = ndctl_bus_wait_probe(bus); if (rc < 0) return NULL; cmd_size = ndctl_dimm_cmd_new_cfg_size(dimm); if (!cmd_size) return NULL; rc = ndctl_cmd_submit_xlat(cmd_size); if (rc < 0) goto out_size; cmd_read = ndctl_dimm_cmd_new_cfg_read(cmd_size); if (!cmd_read) goto out_size; /* * To calc the namespace index size use the minimum label * size which corresponds to the maximum namespace index size. */ init_ndd(ndd, cmd_read, cmd_size); ndd->nslabel_size = 128; rc = ndctl_cmd_cfg_read_set_extent(cmd_read, sizeof_namespace_index(ndd) * 2, 0); if (rc < 0) goto out_read; rc = ndctl_cmd_submit_xlat(cmd_read); if (rc < 0) goto out_read; ndctl_cmd_unref(cmd_size); return cmd_read; out_read: ndctl_cmd_unref(cmd_read); out_size: ndctl_cmd_unref(cmd_size); return NULL; } NDCTL_EXPORT struct ndctl_cmd *ndctl_dimm_read_label_extent( struct ndctl_dimm *dimm, unsigned int len, unsigned int offset) { struct ndctl_bus *bus = ndctl_dimm_get_bus(dimm); struct ndctl_cmd *cmd_size, *cmd_read; int rc; rc = ndctl_bus_wait_probe(bus); if (rc < 0) return NULL; cmd_size = ndctl_dimm_cmd_new_cfg_size(dimm); if (!cmd_size) return NULL; rc = ndctl_cmd_submit_xlat(cmd_size); if (rc < 0) goto out_size; cmd_read = ndctl_dimm_cmd_new_cfg_read(cmd_size); if (!cmd_read) goto out_size; /* * For ndctl_read_labels() compat, enable subsequent calls that * will manipulate labels */ if (len == 0 && offset == 0) init_ndd(&dimm->ndd, cmd_read, cmd_size); if (len == 0) len = cmd_size->get_size->config_size; rc = ndctl_cmd_cfg_read_set_extent(cmd_read, len, offset); if (rc < 0) goto out_read; rc = ndctl_cmd_submit_xlat(cmd_read); if (rc < 0) goto out_read; ndctl_cmd_unref(cmd_size); return cmd_read; out_read: ndctl_cmd_unref(cmd_read); out_size: ndctl_cmd_unref(cmd_size); return NULL; } NDCTL_EXPORT struct ndctl_cmd *ndctl_dimm_read_labels(struct ndctl_dimm *dimm) { return ndctl_dimm_read_label_extent(dimm, 0, 0); } NDCTL_EXPORT int ndctl_dimm_zero_label_extent(struct ndctl_dimm *dimm, unsigned int len, unsigned int offset) { struct ndctl_ctx *ctx = ndctl_dimm_get_ctx(dimm); struct ndctl_cmd *cmd_read, *cmd_write; int rc; cmd_read = ndctl_dimm_read_label_extent(dimm, len, offset); if (!cmd_read) return -EINVAL; if (ndctl_dimm_is_active(dimm)) { dbg(ctx, "%s: regions active, abort label write\n", ndctl_dimm_get_devname(dimm)); rc = -EBUSY; goto out_read; } cmd_write = ndctl_dimm_cmd_new_cfg_write(cmd_read); if (!cmd_write) { rc = -ENOTTY; goto out_read; } if (ndctl_cmd_cfg_write_zero_data(cmd_write) < 0) { rc = -ENXIO; goto out_write; } rc = ndctl_cmd_submit_xlat(cmd_write); if (rc < 0) goto out_write; /* * If the dimm is already disabled the kernel is not holding a cached * copy of the label space. */ if (!ndctl_dimm_is_enabled(dimm)) goto out_write; rc = ndctl_dimm_disable(dimm); if (rc) goto out_write; rc = ndctl_dimm_enable(dimm); out_write: ndctl_cmd_unref(cmd_write); out_read: ndctl_cmd_unref(cmd_read); return rc; } NDCTL_EXPORT int ndctl_dimm_zero_labels(struct ndctl_dimm *dimm) { return ndctl_dimm_zero_label_extent(dimm, 0, 0); } NDCTL_EXPORT unsigned long ndctl_dimm_get_available_labels( struct ndctl_dimm *dimm) { struct ndctl_ctx *ctx = ndctl_dimm_get_ctx(dimm); char *path = dimm->dimm_buf; int rc, len = dimm->buf_len; char buf[SYSFS_ATTR_SIZE]; if (snprintf(path, len, "%s/available_slots", dimm->dimm_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_dimm_get_devname(dimm)); errno = ENOMEM; return ULONG_MAX; } rc = sysfs_read_attr(ctx, path, buf); if (rc < 0) { errno = -rc; return ULONG_MAX; } return strtoul(buf, NULL, 0); } NDCTL_EXPORT enum ndctl_security_state ndctl_dimm_get_security( struct ndctl_dimm *dimm) { struct ndctl_ctx *ctx = ndctl_dimm_get_ctx(dimm); char *path = dimm->dimm_buf; char buf[SYSFS_ATTR_SIZE]; int len = dimm->buf_len; int rc; if (snprintf(path, len, "%s/security", dimm->dimm_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_dimm_get_devname(dimm)); return NDCTL_SECURITY_INVALID; } rc = sysfs_read_attr(ctx, path, buf); if (rc < 0) return NDCTL_SECURITY_INVALID; if (strcmp(buf, "disabled") == 0) return NDCTL_SECURITY_DISABLED; else if (strcmp(buf, "unlocked") == 0) return NDCTL_SECURITY_UNLOCKED; else if (strcmp(buf, "locked") == 0) return NDCTL_SECURITY_LOCKED; else if (strcmp(buf, "frozen") == 0) return NDCTL_SECURITY_FROZEN; else if (strcmp(buf, "overwrite") == 0) return NDCTL_SECURITY_OVERWRITE; return NDCTL_SECURITY_INVALID; } NDCTL_EXPORT bool ndctl_dimm_security_is_frozen(struct ndctl_dimm *dimm) { struct ndctl_ctx *ctx = ndctl_dimm_get_ctx(dimm); char *path = dimm->dimm_buf; char buf[SYSFS_ATTR_SIZE]; int len = dimm->buf_len; int rc; if (ndctl_dimm_get_security(dimm) == NDCTL_SECURITY_FROZEN) return true; if (snprintf(path, len, "%s/frozen", dimm->dimm_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_dimm_get_devname(dimm)); return false; } rc = sysfs_read_attr(ctx, path, buf); if (rc < 0) return false; return !!strtoul(buf, NULL, 0); } static int write_security(struct ndctl_dimm *dimm, const char *cmd) { struct ndctl_ctx *ctx = ndctl_dimm_get_ctx(dimm); char *path = dimm->dimm_buf; int len = dimm->buf_len; if (snprintf(path, len, "%s/security", dimm->dimm_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_dimm_get_devname(dimm)); return -ERANGE; } return sysfs_write_attr(ctx, path, cmd); } NDCTL_EXPORT int ndctl_dimm_update_passphrase(struct ndctl_dimm *dimm, long ckey, long nkey) { char buf[SYSFS_ATTR_SIZE]; sprintf(buf, "update %ld %ld\n", ckey, nkey); return write_security(dimm, buf); } NDCTL_EXPORT int ndctl_dimm_disable_passphrase(struct ndctl_dimm *dimm, long key) { char buf[SYSFS_ATTR_SIZE]; sprintf(buf, "disable %ld\n", key); return write_security(dimm, buf); } NDCTL_EXPORT int ndctl_dimm_disable_master_passphrase(struct ndctl_dimm *dimm, long key) { char buf[SYSFS_ATTR_SIZE]; sprintf(buf, "disable_master %ld\n", key); return write_security(dimm, buf); } NDCTL_EXPORT int ndctl_dimm_freeze_security(struct ndctl_dimm *dimm) { return write_security(dimm, "freeze"); } NDCTL_EXPORT int ndctl_dimm_secure_erase(struct ndctl_dimm *dimm, long key) { char buf[SYSFS_ATTR_SIZE]; sprintf(buf, "erase %ld\n", key); return write_security(dimm, buf); } NDCTL_EXPORT int ndctl_dimm_overwrite(struct ndctl_dimm *dimm, long key) { char buf[SYSFS_ATTR_SIZE]; sprintf(buf, "overwrite %ld\n", key); return write_security(dimm, buf); } NDCTL_EXPORT int ndctl_dimm_wait_overwrite(struct ndctl_dimm *dimm) { struct ndctl_ctx *ctx = ndctl_dimm_get_ctx(dimm); struct pollfd fds; char buf[SYSFS_ATTR_SIZE]; int fd = 0, rc; char *path = dimm->dimm_buf; int len = dimm->buf_len; if (snprintf(path, len, "%s/security", dimm->dimm_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_dimm_get_devname(dimm)); return -ERANGE; } fd = open(path, O_RDONLY|O_CLOEXEC); if (fd < 0) { rc = -errno; err(ctx, "open: %s\n", strerror(errno)); return rc; } memset(&fds, 0, sizeof(fds)); fds.fd = fd; rc = sysfs_read_attr(ctx, path, buf); if (rc < 0) { rc = -EOPNOTSUPP; goto out; } /* skipping if we aren't in overwrite state */ if (strcmp(buf, "overwrite") != 0) { rc = 0; goto out; } for (;;) { rc = sysfs_read_attr(ctx, path, buf); if (rc < 0) { rc = -EOPNOTSUPP; break; } if (strncmp(buf, "overwrite", 9) == 0) { rc = poll(&fds, 1, -1); if (rc < 0) { rc = -errno; err(ctx, "poll error: %s\n", strerror(errno)); break; } dbg(ctx, "poll wake: revents: %d\n", fds.revents); if (pread(fd, buf, 1, 0) == -1) { rc = -errno; break; } fds.revents = 0; } else { if (strncmp(buf, "disabled", 8) == 0) rc = 1; break; } } if (rc == 1) dbg(ctx, "%s: overwrite complete\n", ndctl_dimm_get_devname(dimm)); else if (rc == 0) dbg(ctx, "%s: overwrite skipped\n", ndctl_dimm_get_devname(dimm)); else dbg(ctx, "%s: overwrite error waiting for complete\n", ndctl_dimm_get_devname(dimm)); out: close(fd); return rc; } NDCTL_EXPORT int ndctl_dimm_update_master_passphrase(struct ndctl_dimm *dimm, long ckey, long nkey) { char buf[SYSFS_ATTR_SIZE]; sprintf(buf, "master_update %ld %ld\n", ckey, nkey); return write_security(dimm, buf); } NDCTL_EXPORT int ndctl_dimm_master_secure_erase(struct ndctl_dimm *dimm, long key) { char buf[SYSFS_ATTR_SIZE]; sprintf(buf, "master_erase %ld\n", key); return write_security(dimm, buf); } ndctl-81/ndctl/lib/firmware.c000066400000000000000000000056601476737544500162770ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) 2018-2020 Intel Corporation. All rights reserved. */ #include #include #include #include #include "private.h" /* * Define the wrappers around the ndctl_dimm_ops for firmware update: */ NDCTL_EXPORT struct ndctl_cmd * ndctl_dimm_cmd_new_fw_get_info(struct ndctl_dimm *dimm) { struct ndctl_dimm_ops *ops = dimm->ops; if (ops && ops->new_fw_get_info) return ops->new_fw_get_info(dimm); else return NULL; } NDCTL_EXPORT struct ndctl_cmd * ndctl_dimm_cmd_new_fw_start_update(struct ndctl_dimm *dimm) { struct ndctl_dimm_ops *ops = dimm->ops; if (ops && ops->new_fw_start_update) return ops->new_fw_start_update(dimm); else return NULL; } NDCTL_EXPORT struct ndctl_cmd * ndctl_dimm_cmd_new_fw_send(struct ndctl_cmd *start, unsigned int offset, unsigned int len, void *data) { struct ndctl_dimm_ops *ops = start->dimm->ops; if (ops && ops->new_fw_send) return ops->new_fw_send(start, offset, len, data); else return NULL; } NDCTL_EXPORT struct ndctl_cmd * ndctl_dimm_cmd_new_fw_finish(struct ndctl_cmd *start) { struct ndctl_dimm_ops *ops = start->dimm->ops; if (ops && ops->new_fw_finish) return ops->new_fw_finish(start); else return NULL; } NDCTL_EXPORT struct ndctl_cmd * ndctl_dimm_cmd_new_fw_abort(struct ndctl_cmd *start) { struct ndctl_dimm_ops *ops = start->dimm->ops; if (ops && ops->new_fw_finish) return ops->new_fw_abort(start); else return NULL; } NDCTL_EXPORT struct ndctl_cmd * ndctl_dimm_cmd_new_fw_finish_query(struct ndctl_cmd *start) { struct ndctl_dimm_ops *ops = start->dimm->ops; if (ops && ops->new_fw_finish_query) return ops->new_fw_finish_query(start); else return NULL; } #define firmware_cmd_op(op, rettype, defretvalue) \ NDCTL_EXPORT rettype ndctl_cmd_##op(struct ndctl_cmd *cmd) \ { \ if (cmd->dimm) { \ struct ndctl_dimm_ops *ops = cmd->dimm->ops; \ if (ops && ops->op) \ return ops->op(cmd); \ } \ return defretvalue; \ } firmware_cmd_op(fw_info_get_storage_size, unsigned int, 0) firmware_cmd_op(fw_info_get_max_send_len, unsigned int, 0) firmware_cmd_op(fw_info_get_query_interval, unsigned int, 0) firmware_cmd_op(fw_info_get_max_query_time, unsigned int, 0) firmware_cmd_op(fw_info_get_run_version, unsigned long long, 0) firmware_cmd_op(fw_info_get_updated_version, unsigned long long, 0) firmware_cmd_op(fw_start_get_context, unsigned int, 0) firmware_cmd_op(fw_fquery_get_fw_rev, unsigned long long, 0) NDCTL_EXPORT enum ND_FW_STATUS ndctl_cmd_fw_xlat_firmware_status(struct ndctl_cmd *cmd) { struct ndctl_dimm_ops *ops = cmd->dimm->ops; if (ops && ops->fw_xlat_firmware_status) return ops->fw_xlat_firmware_status(cmd); else return FW_EUNKNOWN; } NDCTL_EXPORT int ndctl_dimm_fw_update_supported(struct ndctl_dimm *dimm) { struct ndctl_dimm_ops *ops = dimm->ops; if (ops && ops->fw_update_supported) return ops->fw_update_supported(dimm); else return -ENOTTY; } ndctl-81/ndctl/lib/hpe1.c000066400000000000000000000216651476737544500153230ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-2.1 // Copyright (C) 2016 Hewlett Packard Enterprise Development LP // Copyright (C) 2016-2020, Intel Corporation. #include #include #include #include #include "private.h" #include "hpe1.h" #define CMD_HPE1(_c) ((_c)->hpe1) #define CMD_HPE1_SMART(_c) (CMD_HPE1(_c)->u.smart.data) #define CMD_HPE1_SMART_THRESH(_c) (CMD_HPE1(_c)->u.thresh.data) static u32 hpe1_get_firmware_status(struct ndctl_cmd *cmd) { switch (cmd->hpe1->gen.nd_command) { case NDN_HPE1_CMD_SMART: return cmd->hpe1->u.smart.status; case NDN_HPE1_CMD_SMART_THRESHOLD: return cmd->hpe1->u.thresh.status; } return -1U; } static struct ndctl_cmd *hpe1_dimm_cmd_new_smart(struct ndctl_dimm *dimm) { struct ndctl_bus *bus = ndctl_dimm_get_bus(dimm); struct ndctl_ctx *ctx = ndctl_bus_get_ctx(bus); struct ndctl_cmd *cmd; size_t size; struct ndn_pkg_hpe1 *hpe1; if (!ndctl_dimm_is_cmd_supported(dimm, ND_CMD_CALL)) { dbg(ctx, "unsupported cmd\n"); return NULL; } if (test_dimm_dsm(dimm, NDN_HPE1_CMD_SMART) == DIMM_DSM_UNSUPPORTED) { dbg(ctx, "unsupported function\n"); return NULL; } size = sizeof(*cmd) + sizeof(struct ndn_pkg_hpe1); cmd = calloc(1, size); if (!cmd) return NULL; cmd->dimm = dimm; ndctl_cmd_ref(cmd); cmd->type = ND_CMD_CALL; cmd->size = size; cmd->status = 1; hpe1 = CMD_HPE1(cmd); hpe1->gen.nd_family = NVDIMM_FAMILY_HPE1; hpe1->gen.nd_command = NDN_HPE1_CMD_SMART; hpe1->gen.nd_fw_size = 0; hpe1->gen.nd_size_in = offsetof(struct ndn_hpe1_smart, status); hpe1->gen.nd_size_out = sizeof(hpe1->u.smart); hpe1->u.smart.status = 3; cmd->get_firmware_status = hpe1_get_firmware_status; hpe1->u.smart.in_valid_flags = 0; hpe1->u.smart.in_valid_flags |= NDN_HPE1_SMART_HEALTH_VALID; hpe1->u.smart.in_valid_flags |= NDN_HPE1_SMART_TEMP_VALID; hpe1->u.smart.in_valid_flags |= NDN_HPE1_SMART_SPARES_VALID; hpe1->u.smart.in_valid_flags |= NDN_HPE1_SMART_ALARM_VALID; hpe1->u.smart.in_valid_flags |= NDN_HPE1_SMART_USED_VALID; hpe1->u.smart.in_valid_flags |= NDN_HPE1_SMART_SHUTDOWN_VALID; hpe1->u.smart.in_valid_flags |= NDN_HPE1_SMART_VENDOR_VALID; return cmd; } static int hpe1_smart_valid(struct ndctl_cmd *cmd) { if (cmd->type != ND_CMD_CALL || cmd->size != sizeof(*cmd) + sizeof(struct ndn_pkg_hpe1) || CMD_HPE1(cmd)->gen.nd_family != NVDIMM_FAMILY_HPE1 || CMD_HPE1(cmd)->gen.nd_command != NDN_HPE1_CMD_SMART || cmd->status != 0) return cmd->status < 0 ? cmd->status : -EINVAL; return 0; } static unsigned int hpe1_cmd_smart_get_flags(struct ndctl_cmd *cmd) { unsigned int hpe1flags; unsigned int flags; int rc; rc = hpe1_smart_valid(cmd); if (rc < 0) { errno = -rc; return UINT_MAX; } hpe1flags = CMD_HPE1_SMART(cmd)->out_valid_flags; flags = 0; if (hpe1flags & NDN_HPE1_SMART_HEALTH_VALID) flags |= ND_SMART_HEALTH_VALID; if (hpe1flags & NDN_HPE1_SMART_TEMP_VALID) flags |= ND_SMART_TEMP_VALID ; if (hpe1flags & NDN_HPE1_SMART_SPARES_VALID) flags |= ND_SMART_SPARES_VALID; if (hpe1flags & NDN_HPE1_SMART_ALARM_VALID) flags |= ND_SMART_ALARM_VALID; if (hpe1flags & NDN_HPE1_SMART_USED_VALID) flags |= ND_SMART_USED_VALID; if (hpe1flags & NDN_HPE1_SMART_SHUTDOWN_VALID) flags |= ND_SMART_SHUTDOWN_VALID; if (hpe1flags & NDN_HPE1_SMART_VENDOR_VALID) flags |= ND_SMART_VENDOR_VALID; return flags; } static unsigned int hpe1_cmd_smart_get_health(struct ndctl_cmd *cmd) { unsigned char hpe1health; unsigned int health; int rc; rc = hpe1_smart_valid(cmd); if (rc < 0) { errno = -rc; return UINT_MAX; } hpe1health = CMD_HPE1_SMART(cmd)->stat_summary; health = 0; if (hpe1health & NDN_HPE1_SMART_NONCRIT_HEALTH) health |= ND_SMART_NON_CRITICAL_HEALTH;; if (hpe1health & NDN_HPE1_SMART_CRITICAL_HEALTH) health |= ND_SMART_CRITICAL_HEALTH; if (hpe1health & NDN_HPE1_SMART_FATAL_HEALTH) health |= ND_SMART_FATAL_HEALTH; return health; } static unsigned int hpe1_cmd_smart_get_media_temperature(struct ndctl_cmd *cmd) { int rc; rc = hpe1_smart_valid(cmd); if (rc < 0) { errno = -rc; return UINT_MAX; } return CMD_HPE1_SMART(cmd)->curr_temp; } static unsigned int hpe1_cmd_smart_get_spares(struct ndctl_cmd *cmd) { int rc; rc = hpe1_smart_valid(cmd); if (rc < 0) { errno = -rc; return UINT_MAX; } return CMD_HPE1_SMART(cmd)->spare_blocks; } static unsigned int hpe1_cmd_smart_get_alarm_flags(struct ndctl_cmd *cmd) { unsigned int hpe1flags; unsigned int flags; int rc; rc = hpe1_smart_valid(cmd); if (rc < 0) { errno = -rc; return UINT_MAX; } hpe1flags = CMD_HPE1_SMART(cmd)->alarm_trips; flags = 0; if (hpe1flags & NDN_HPE1_SMART_TEMP_TRIP) flags |= ND_SMART_TEMP_TRIP; if (hpe1flags & NDN_HPE1_SMART_SPARE_TRIP) flags |= ND_SMART_SPARE_TRIP; return flags; } static unsigned int hpe1_cmd_smart_get_life_used(struct ndctl_cmd *cmd) { int rc; rc = hpe1_smart_valid(cmd); if (rc < 0) { errno = -rc; return UINT_MAX; } return CMD_HPE1_SMART(cmd)->device_life; } static unsigned int hpe1_cmd_smart_get_shutdown_state(struct ndctl_cmd *cmd) { unsigned int shutdown; int rc; rc = hpe1_smart_valid(cmd); if (rc < 0) { errno = -rc; return UINT_MAX; } shutdown = CMD_HPE1_SMART(cmd)->last_shutdown_stat; if (shutdown == NDN_HPE1_SMART_LASTSAVEGOOD) return 0; else return 1; } static unsigned int hpe1_cmd_smart_get_vendor_size(struct ndctl_cmd *cmd) { int rc; rc = hpe1_smart_valid(cmd); if (rc < 0) { errno = -rc; return UINT_MAX; } return CMD_HPE1_SMART(cmd)->vndr_spec_data_size; } static unsigned char *hpe1_cmd_smart_get_vendor_data(struct ndctl_cmd *cmd) { int rc; rc = hpe1_smart_valid(cmd); if (rc < 0) { errno = -rc; return NULL; } return CMD_HPE1_SMART(cmd)->vnd_spec_data; } static struct ndctl_cmd *hpe1_dimm_cmd_new_smart_threshold(struct ndctl_dimm *dimm) { struct ndctl_bus *bus = ndctl_dimm_get_bus(dimm); struct ndctl_ctx *ctx = ndctl_bus_get_ctx(bus); struct ndctl_cmd *cmd; size_t size; struct ndn_pkg_hpe1 *hpe1; if (!ndctl_dimm_is_cmd_supported(dimm, ND_CMD_CALL)) { dbg(ctx, "unsupported cmd\n"); return NULL; } if (test_dimm_dsm(dimm, NDN_HPE1_CMD_SMART_THRESHOLD) == DIMM_DSM_UNSUPPORTED) { dbg(ctx, "unsupported function\n"); return NULL; } size = sizeof(*cmd) + sizeof(struct ndn_pkg_hpe1); cmd = calloc(1, size); if (!cmd) return NULL; cmd->dimm = dimm; ndctl_cmd_ref(cmd); cmd->type = ND_CMD_CALL; cmd->size = size; cmd->status = 1; hpe1 = CMD_HPE1(cmd); hpe1->gen.nd_family = NVDIMM_FAMILY_HPE1; hpe1->gen.nd_command = NDN_HPE1_CMD_SMART_THRESHOLD; hpe1->gen.nd_fw_size = 0; hpe1->gen.nd_size_in = offsetof(struct ndn_hpe1_smart_threshold, status); hpe1->gen.nd_size_out = sizeof(hpe1->u.smart); hpe1->u.thresh.status = 3; cmd->get_firmware_status = hpe1_get_firmware_status; return cmd; } static int hpe1_smart_threshold_valid(struct ndctl_cmd *cmd) { if (cmd->type != ND_CMD_CALL || cmd->size != sizeof(*cmd) + sizeof(struct ndn_pkg_hpe1) || CMD_HPE1(cmd)->gen.nd_family != NVDIMM_FAMILY_HPE1 || CMD_HPE1(cmd)->gen.nd_command != NDN_HPE1_CMD_SMART_THRESHOLD || cmd->status != 0) return cmd->status < 0 ? cmd->status : -EINVAL; return 0; } static unsigned int hpe1_cmd_smart_threshold_get_alarm_control(struct ndctl_cmd *cmd) { unsigned int hpe1flags; unsigned int flags; int rc; rc = hpe1_smart_threshold_valid(cmd); if (rc < 0) { errno = -rc; return UINT_MAX; } hpe1flags = CMD_HPE1_SMART_THRESH(cmd)->threshold_alarm_ctl; flags = 0; if (hpe1flags & NDN_HPE1_SMART_TEMP_TRIP) flags |= ND_SMART_TEMP_TRIP; if (hpe1flags & NDN_HPE1_SMART_SPARE_TRIP) flags |= ND_SMART_SPARE_TRIP; return flags; } static unsigned int hpe1_cmd_smart_threshold_get_media_temperature( struct ndctl_cmd *cmd) { int rc; rc = hpe1_smart_threshold_valid(cmd); if (rc < 0) { errno = -rc; return UINT_MAX; } return CMD_HPE1_SMART_THRESH(cmd)->temp_threshold; } static unsigned int hpe1_cmd_smart_threshold_get_spares(struct ndctl_cmd *cmd) { int rc; rc = hpe1_smart_threshold_valid(cmd); if (rc < 0) { errno = -rc; return UINT_MAX; } return CMD_HPE1_SMART_THRESH(cmd)->spare_block_threshold; } struct ndctl_dimm_ops * const hpe1_dimm_ops = &(struct ndctl_dimm_ops) { .new_smart = hpe1_dimm_cmd_new_smart, .smart_get_flags = hpe1_cmd_smart_get_flags, .smart_get_health = hpe1_cmd_smart_get_health, .smart_get_media_temperature = hpe1_cmd_smart_get_media_temperature, .smart_get_spares = hpe1_cmd_smart_get_spares, .smart_get_alarm_flags = hpe1_cmd_smart_get_alarm_flags, .smart_get_life_used = hpe1_cmd_smart_get_life_used, .smart_get_shutdown_state = hpe1_cmd_smart_get_shutdown_state, .smart_get_vendor_size = hpe1_cmd_smart_get_vendor_size, .smart_get_vendor_data = hpe1_cmd_smart_get_vendor_data, .new_smart_threshold = hpe1_dimm_cmd_new_smart_threshold, .smart_threshold_get_alarm_control = hpe1_cmd_smart_threshold_get_alarm_control, .smart_threshold_get_media_temperature = hpe1_cmd_smart_threshold_get_media_temperature, .smart_threshold_get_spares = hpe1_cmd_smart_threshold_get_spares, }; ndctl-81/ndctl/lib/hpe1.h000066400000000000000000000213531476737544500153220ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1 */ /* Copyright (C) 2016 Hewlett Packard Enterprise Development LP */ /* Copyright (C) 2014-2020, Intel Corporation */ #ifndef __NDCTL_HPE1_H__ #define __NDCTL_HPE1_H__ enum { NDN_HPE1_CMD_QUERY = 0, /* non-root commands */ NDN_HPE1_CMD_SMART = 1, NDN_HPE1_CMD_SMART_THRESHOLD = 2, NDN_HPE1_CMD_GET_CONFIG_SIZE = 4, NDN_HPE1_CMD_GET_CONFIG_DATA = 5, NDN_HPE1_CMD_SET_CONFIG_DATA = 6, NDN_HPE1_CMD_GET_IDENT = 10, NDN_HPE1_CMD_GET_ES_IDENT = 11, NDN_HPE1_CMD_GET_LAST_BACKUP = 12, NDN_HPE1_CMD_SET_LIFE_THRESHOLD = 13, NDN_HPE1_CMD_ERRINJ_QUERY = 18, NDN_HPE1_CMD_ERRINJ_INJECT = 19, NDN_HPE1_CMD_ERRINJ_STATUS = 20, }; /* NDN_HPE1_CMD_SMART */ /* ndn_hpe1_smart.in_valid_flags / ndn_hpe1_smart_data.out_valid_flags */ #define NDN_HPE1_SMART_HEALTH_VALID (1 << 0) #define NDN_HPE1_SMART_TEMP_VALID (1 << 1) #define NDN_HPE1_SMART_SPARES_VALID (1 << 2) #define NDN_HPE1_SMART_ALARM_VALID (1 << 3) #define NDN_HPE1_SMART_USED_VALID (1 << 4) #define NDN_HPE1_SMART_SHUTDOWN_VALID (1 << 5) #define NDN_HPE1_SMART_STATS_VALID (1 << 6) #define NDN_HPE1_SMART_DETAIL_VALID (1 << 7) #define NDN_HPE1_SMART_ENERGY_VALID (1 << 8) #define NDN_HPE1_SMART_VENDOR_VALID (1 << 9) #define NDN_HPE1_SMART_NOTIFIED (1 << 31) /* ndn_hpe1_smart_data.stat_summary */ #define NDN_HPE1_SMART_NONCRIT_HEALTH (1 << 0) #define NDN_HPE1_SMART_CRITICAL_HEALTH (1 << 1) #define NDN_HPE1_SMART_FATAL_HEALTH (1 << 2) /* ndn_hpe1_smart_data.alarm_trips */ #define NDN_HPE1_SMART_TEMP_TRIP (1 << 0) #define NDN_HPE1_SMART_SPARE_TRIP (1 << 1) #define NDN_HPE1_SMART_LIFEWARN_TRIP (1 << 2) #define NDN_HPE1_SMART_LIFEERR_TRIP (1 << 3) #define NDN_HPE1_SMART_ESLIFEWARN_TRIP (1 << 4) #define NDN_HPE1_SMART_ESLIFEERR_TRIP (1 << 5) #define NDN_HPE1_SMART_ESTEMPWARN_TRIP (1 << 6) #define NDN_HPE1_SMART_ESTEMPERR_TRIP (1 << 7) /* ndn_hpe1_smart_data.last_shutdown_stat */ #define NDN_HPE1_SMART_LASTSAVEGOOD (1 << 1) /* ndn_hpe1_smart_data.mod_hlth_stat */ #define NDN_HPE1_SMART_ES_FAILURE (1 << 0) #define NDN_HPE1_SMART_CTLR_FAILURE (1 << 1) #define NDN_HPE1_SMART_UE_TRIP (1 << 2) #define NDN_HPE1_SMART_CE_TRIP (1 << 3) #define NDN_HPE1_SMART_SAVE_FAILED (1 << 4) #define NDN_HPE1_SMART_RESTORE_FAILED (1 << 5) #define NDN_HPE1_SMART_ARM_FAILED (1 << 6) #define NDN_HPE1_SMART_ERASE_FAILED (1 << 7) #define NDN_HPE1_SMART_CONFIG_ERROR (1 << 8) #define NDN_HPE1_SMART_FW_ERROR (1 << 9) #define NDN_HPE1_SMART_VENDOR_ERROR (1 << 10) struct ndn_hpe1_smart_data { __u32 out_valid_flags; __u8 stat_summary; __u16 curr_temp; __u8 spare_blocks; __u16 alarm_trips; __u8 device_life; __u8 last_shutdown_stat; __u16 last_save_op_dur; __u16 last_restore_op_dur; __u16 last_erase_op_dur; __u16 res1; __u32 save_ops; __u32 restore_ops; __u32 erase_ops; __u32 life_save_ops; __u32 life_restore_ops; __u32 life_erase_ops; __u32 life_mod_pwr_cycles; __u32 mod_hlth_stat; __u32 energy_src_check; __u8 energy_src_life_percent; __u16 energy_src_curr_temp; __u8 res2; __u16 energy_src_total_runtime; __u16 vndr_spec_data_size; __u8 vnd_spec_data[60]; } __attribute__((packed)); struct ndn_hpe1_smart { __u32 in_valid_flags; __u32 status; union { __u8 buf[124]; struct ndn_hpe1_smart_data data[1]; }; } __attribute__((packed)); /* NDN_HPE1_CMD_SMART_THRESHOLD */ struct ndn_hpe1_smart_threshold_data { __u16 threshold_alarm_ctl; __u16 temp_threshold; __u8 spare_block_threshold; __u8 res1[3]; __u8 dev_lifewarn_threshold; __u8 dev_lifeerr_threshold; __u8 res2[6]; __u8 es_lifewarn_threshold; __u8 es_lifeerr_threshold; __u8 es_tempwarn_threshold; __u8 es_temperr_threshold; __u8 res3[4]; __u64 res4; } __attribute__((packed)); struct ndn_hpe1_smart_threshold { __u32 status; union { __u8 buf[32]; struct ndn_hpe1_smart_threshold_data data[1]; }; } __attribute__((packed)); /* NDN_HPE1_CMD_GET_CONFIG_SIZE */ struct ndn_hpe1_get_config_size { __u32 status; __u32 config_size; __u32 max_xfer; } __attribute__((packed)); /* NDN_HPE1_CMD_GET_CONFIG_DATA */ struct ndn_hpe1_get_config_data_hdr { __u32 in_offset; __u32 in_length; __u32 status; __u8 out_buf[0]; } __attribute__((packed)); /* NDN_HPE1_CMD_SET_CONFIG_DATA */ struct ndn_hpe1_set_config_hdr { __u32 in_offset; __u32 in_length; __u8 in_buf[0]; } __attribute__((packed)); /* ndn_hpe1_get_id.sup_backup_trigger */ #define NDN_HPE1_BKUP_SUPPORT_CKE (1 << 0) #define NDN_HPE1_BKUP_SUPPORT_EXTERNAL (1 << 1) #define NDN_HPE1_BKUP_SUPPORT_12V (1 << 2) #define NDN_HPE1_BKUP_SUPPORT_I2C (1 << 3) #define NDN_HPE1_BKUP_SUPPORT_SAVEN (1 << 4) /* NDN_HPE1_CMD_GET_IDENT */ struct ndn_hpe1_get_id { __u32 status; __u8 spec_rev; __u8 num_stnd_pages; __u32 hw_rev; __u8 sup_backup_trigger; __u16 max_op_retries; __u8 __res1[3]; __u32 backup_op_timeout; __u32 restore_op_timeout; __u32 erase_op_timeout; __u32 arm_op_timeout; __u32 fw_op_timeout; __u32 region_block_size; __u16 min_op_temp; __u16 max_op_temp; __u8 curr_fw_slot; __u8 res2[1]; __u16 num_fw_slots; __u8 fw_slot_revision[0]; } __attribute__((packed)); /* ndn_hpe1_get_energy_src_id.attr */ #define NDN_HPE1_ES_ATTR_BUILTIN (1 << 0) #define NDN_HPE1_ES_ATTR_TETHERED (1 << 1) #define NDN_HPE1_ES_ATTR_SHARED (1 << 2) /* ndn_hpe1_get_energy_src_id.tech */ #define NDN_HPE1_ES_TECH_UNDEFINED (1 << 0) #define NDN_HPE1_ES_TECH_SUPERCAP (1 << 1) #define NDN_HPE1_ES_TECH_BATTERY (1 << 2) #define NDN_HPE1_ES_TECH_HYBRIDCAP (1 << 3) /* NDN_HPE1_CMD_GET_ES_IDENT */ struct ndn_hpe1_get_energy_src_id { __u32 status; __u8 energy_src_policy; __u8 attr; __u8 tech; __u8 reserved; __u16 hw_rev; __u16 fw_rev; __u32 charge_timeout; __u16 min_op_temp; __u16 max_op_temp; } __attribute__((packed)); /* ndn_hpe1_last_backup_info.last_backup_initiation */ #define NDN_HPE1_LASTBKUP_SAVEN (1 << 0) #define NDN_HPE1_LASTBKUP_EXTERNAL (1 << 1) #define NDN_HPE1_LASTBKUP_CKE (1 << 2) #define NDN_HPE1_LASTBKUP_FW (1 << 3) #define NDN_HPE1_LASTBKUP_RESETN (1 << 4) /* ndn_hpe1_last_backup_info.ctlr_backup_stat */ #define NDN_HPE1_LASTBKUP_GTG (1 << 0) #define NDN_HPE1_LASTBKUP_SDRAM_FAULT (1 << 1) #define NDN_HPE1_LASTBKUP_GEN_FAULT (1 << 2) /* NDN_HPE1_CMD_GET_LAST_BACKUP */ struct ndn_hpe1_get_last_backup { __u32 status; __u32 last_backup_info; } __attribute__((packed)); struct ndn_hpe1_last_backup_info { __u8 backup_image; __u8 backup_cmplt_stat; __u8 last_backup_initiation; __u8 ctlr_backup_stat; } __attribute__((packed)); /* NDN_HPE1_CMD_SET_LIFE_THRESHOLD */ struct ndn_hpe1_set_lifetime_threshold { __u8 in_nvm_lifetime_warn_threshold; __u32 status; } __attribute__((packed)); /* ndn_hpe1_inj_err.in_err_typ * ndn_hpe1_get_inj_err_status.err_inj_type * log2(ndn_hpe1_query_err_inj_cap.err_inj_cap) */ enum { NDN_HPE1_EINJ_DEV_NONCRIT = 1, NDN_HPE1_EINJ_DEV_CRIT = 2, NDN_HPE1_EINJ_DEV_FATAL = 3, NDN_HPE1_EINJ_UE_BACKUP = 4, NDN_HPE1_EINJ_UE_RESTORE = 5, NDN_HPE1_EINJ_UE_ERASE = 6, NDN_HPE1_EINJ_UE_ARM = 7, NDN_HPE1_EINJ_BADBLOCK = 8, NDN_HPE1_EINJ_ES_FAULT = 9, NDN_HPE1_EINJ_ES_LOWCHARGE = 10, NDN_HPE1_EINJ_ES_TEMPWARN = 11, NDN_HPE1_EINJ_ES_TEMPERR = 12, NDN_HPE1_EINJ_ES_LIFEWARN = 13, NDN_HPE1_EINJ_ES_LIFEERR = 14, NDN_HPE1_EINJ_DEV_LIFEWARN = 15, NDN_HPE1_EINJ_DEV_LIFEERR = 16, NDN_HPE1_EINJ_FWUPDATE_ERR = 17, NDN_HPE1_EINJ_CTRL_ERR = 18, }; /* ndn_hpe1_inj_err.in_options / ndn_hpe1_get_inj_err_status.err_inj_opt */ enum { NDN_HPE1_EINJ_OPT_SINGLE = 0, NDN_HPE1_EINJ_OPT_PERSISTENT = 1, NDN_HPE1_EINJ_OPT_CLEAR = 2, }; /* ndn_hpe1_get_inj_err_status.err_inj_stat_info */ enum { NDN_HPE1_EINJ_STAT_NONE = 0, NDN_HPE1_EINJ_STAT_INJECTED = 1, NDN_HPE1_EINJ_STAT_PENDING = 2, }; /* NDN_HPE1_CMD_ERRINJ_QUERY */ struct ndn_hpe1_query_err_inj_cap { __u32 status; __u8 err_inj_cap[32]; } __attribute__((packed)); /* NDN_HPE1_CMD_ERRINJ_INJECT */ struct ndn_hpe1_inj_err { __u8 in_err_typ; __u8 in_options; __u32 status; } __attribute__((packed)); /* NDN_HPE1_CMD_ERRINJ_STATUS */ struct ndn_hpe1_get_inj_err_status { __u32 status; __u8 err_inj_stat_info; __u8 err_inj_type; __u8 err_inj_opt; } __attribute__((packed)); union ndn_hpe1_cmd { __u64 query; struct ndn_hpe1_smart smart; struct ndn_hpe1_smart_threshold thresh; struct ndn_hpe1_get_config_size get_size; struct ndn_hpe1_get_config_data_hdr get_data; struct ndn_hpe1_get_id get_id; struct ndn_hpe1_get_energy_src_id get_energy_src_id; struct ndn_hpe1_get_last_backup get_last_backup; struct ndn_hpe1_last_backup_info last_backup_info; struct ndn_hpe1_set_lifetime_threshold set_life_thresh; struct ndn_hpe1_query_err_inj_cap err_cap; struct ndn_hpe1_inj_err inj_err; struct ndn_hpe1_get_inj_err_status inj_err_stat; unsigned char buf[128]; }; struct ndn_pkg_hpe1 { struct nd_cmd_pkg gen; union ndn_hpe1_cmd u; } __attribute__((packed)); #define NDN_IOCTL_HPE1_PASSTHRU _IOWR(ND_IOCTL, ND_CMD_CALL, \ struct ndn_pkg_hpe1) #endif /* __NDCTL_HPE1_H__ */ ndctl-81/ndctl/lib/hyperv.c000066400000000000000000000106121476737544500157710ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) 2019, Microsoft Corporation. All rights reserved. */ #include #include #include #include #include #include "private.h" #include "hyperv.h" static u32 hyperv_get_firmware_status(struct ndctl_cmd *cmd) { return cmd->hyperv->u.status; } static bool hyperv_cmd_is_supported(struct ndctl_dimm *dimm, int cmd) { /* * "ndctl monitor" requires ND_CMD_SMART, which is not really supported * by Hyper-V virtual NVDIMM. Nevertheless, ND_CMD_SMART can be emulated * by ND_HYPERV_CMD_GET_HEALTH_INFO and ND_HYPERV_CMD_GET_SHUTDOWN_INFO. */ if (cmd == ND_CMD_SMART ) return true; return !!(dimm->cmd_mask & (1ULL << cmd)); } static struct ndctl_cmd *alloc_hyperv_cmd(struct ndctl_dimm *dimm, unsigned int command) { struct ndctl_bus *bus = ndctl_dimm_get_bus(dimm); struct ndctl_ctx *ctx = ndctl_bus_get_ctx(bus); struct nd_pkg_hyperv *hyperv; struct ndctl_cmd *cmd; size_t size; if (!ndctl_dimm_is_cmd_supported(dimm, ND_CMD_CALL)) { dbg(ctx, "unsupported cmd\n"); return NULL; } if (test_dimm_dsm(dimm, command) == DIMM_DSM_UNSUPPORTED) { dbg(ctx, "unsupported function\n"); return NULL; } size = sizeof(*cmd) + sizeof(struct nd_pkg_hyperv); cmd = calloc(1, size); if (!cmd) return NULL; ndctl_cmd_ref(cmd); cmd->dimm = dimm; cmd->type = ND_CMD_CALL; cmd->get_firmware_status = hyperv_get_firmware_status; cmd->size = size; cmd->status = 1; hyperv = cmd->hyperv; hyperv->gen.nd_family = NVDIMM_FAMILY_HYPERV; hyperv->gen.nd_command = command; hyperv->gen.nd_size_out = sizeof(hyperv->u.health_info); return cmd; } static struct ndctl_cmd *hyperv_dimm_cmd_new_smart(struct ndctl_dimm *dimm) { return alloc_hyperv_cmd(dimm, ND_HYPERV_CMD_GET_HEALTH_INFO); } static int hyperv_cmd_valid(struct ndctl_cmd *cmd, unsigned int command) { if (cmd->type != ND_CMD_CALL || cmd->size != sizeof(*cmd) + sizeof(struct nd_pkg_hyperv) || cmd->hyperv->gen.nd_family != NVDIMM_FAMILY_HYPERV || cmd->hyperv->gen.nd_command != command || cmd->status != 0 || cmd->hyperv->u.status != 0) return cmd->status < 0 ? cmd->status : -EINVAL; return 0; } static int hyperv_valid_health_info(struct ndctl_cmd *cmd) { return hyperv_cmd_valid(cmd, ND_HYPERV_CMD_GET_HEALTH_INFO); } static int hyperv_get_shutdown_count(struct ndctl_cmd *cmd, unsigned int *count) { unsigned int command = ND_HYPERV_CMD_GET_SHUTDOWN_INFO; struct ndctl_cmd *cmd_get_shutdown_info; int rc; cmd_get_shutdown_info = alloc_hyperv_cmd(cmd->dimm, command); if (!cmd_get_shutdown_info) return -EINVAL; if (ndctl_cmd_submit_xlat(cmd_get_shutdown_info) < 0 || hyperv_cmd_valid(cmd_get_shutdown_info, command) < 0) { rc = -EINVAL; goto out; } *count = cmd_get_shutdown_info->hyperv->u.shutdown_info.count; rc = 0; out: ndctl_cmd_unref(cmd_get_shutdown_info); return rc; } static unsigned int hyperv_cmd_get_flags(struct ndctl_cmd *cmd) { unsigned int flags = 0; unsigned int count; int rc; rc = hyperv_valid_health_info(cmd); if (rc < 0) { errno = -rc; return 0; } flags |= ND_SMART_HEALTH_VALID; if (hyperv_get_shutdown_count(cmd, &count) == 0) flags |= ND_SMART_SHUTDOWN_COUNT_VALID; return flags; } static unsigned int hyperv_cmd_get_health(struct ndctl_cmd *cmd) { unsigned int health = 0; __u32 num; int rc; rc = hyperv_valid_health_info(cmd); if (rc < 0) { errno = -rc; return UINT_MAX; } num = cmd->hyperv->u.health_info.health & 0x3F; if (num & (BIT(0) | BIT(1))) health |= ND_SMART_CRITICAL_HEALTH; if (num & BIT(2)) health |= ND_SMART_FATAL_HEALTH; if (num & (BIT(3) | BIT(4) | BIT(5))) health |= ND_SMART_NON_CRITICAL_HEALTH; return health; } static unsigned int hyperv_cmd_get_shutdown_count(struct ndctl_cmd *cmd) { unsigned int count; int rc;; rc = hyperv_get_shutdown_count(cmd, &count); if (rc < 0) { errno = -rc; return UINT_MAX; } return count; } static int hyperv_cmd_xlat_firmware_status(struct ndctl_cmd *cmd) { return cmd->hyperv->u.status == 0 ? 0 : -EINVAL; } struct ndctl_dimm_ops * const hyperv_dimm_ops = &(struct ndctl_dimm_ops) { .cmd_is_supported = hyperv_cmd_is_supported, .new_smart = hyperv_dimm_cmd_new_smart, .smart_get_flags = hyperv_cmd_get_flags, .smart_get_health = hyperv_cmd_get_health, .smart_get_shutdown_count = hyperv_cmd_get_shutdown_count, .xlat_firmware_status = hyperv_cmd_xlat_firmware_status, }; ndctl-81/ndctl/lib/hyperv.h000066400000000000000000000016531476737544500160030ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (C) 2019, Microsoft Corporation. All rights reserved. */ #ifndef __NDCTL_HYPERV_H__ #define __NDCTL_HYPERV_H__ /* See http://www.uefi.org/RFIC_LIST ("Virtual NVDIMM 0x1901") */ enum { ND_HYPERV_CMD_QUERY_SUPPORTED_FUNCTIONS = 0, ND_HYPERV_CMD_GET_HEALTH_INFO = 1, ND_HYPERV_CMD_GET_SHUTDOWN_INFO = 2, }; /* Get Health Information (Function Index 1) */ struct nd_hyperv_health_info { __u32 status; __u32 health; } __attribute__((packed)); /* Get Unsafe Shutdown Count (Function Index 2) */ struct nd_hyperv_shutdown_info { __u32 status; __u32 count; } __attribute__((packed)); union nd_hyperv_cmd { __u32 status; struct nd_hyperv_health_info health_info; struct nd_hyperv_shutdown_info shutdown_info; } __attribute__((packed)); struct nd_pkg_hyperv { struct nd_cmd_pkg gen; union nd_hyperv_cmd u; } __attribute__((packed)); #endif /* __NDCTL_HYPERV_H__ */ ndctl-81/ndctl/lib/inject.c000066400000000000000000000315651476737544500157420ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-2.1 // Copyright (C) 2014-2020, Intel Corporation. All rights reserved. #include #include #include #include #include #include #include #include "private.h" NDCTL_EXPORT int ndctl_bus_has_error_injection(struct ndctl_bus *bus) { /* Currently, only nfit buses have error injection */ if (!bus || !ndctl_bus_has_nfit(bus)) return 0; if (ndctl_bus_is_nfit_cmd_supported(bus, NFIT_CMD_ARS_INJECT_SET) && ndctl_bus_is_nfit_cmd_supported(bus, NFIT_CMD_ARS_INJECT_GET) && ndctl_bus_is_nfit_cmd_supported(bus, NFIT_CMD_ARS_INJECT_CLEAR)) return 1; return 0; } static int ndctl_namespace_get_injection_bounds( struct ndctl_namespace *ndns, unsigned long long *ns_offset, unsigned long long *ns_size) { struct ndctl_pfn *pfn = ndctl_namespace_get_pfn(ndns); struct ndctl_dax *dax = ndctl_namespace_get_dax(ndns); struct ndctl_btt *btt = ndctl_namespace_get_btt(ndns); if (!ns_offset || !ns_size) return -ENXIO; if (pfn) { *ns_offset = ndctl_pfn_get_resource(pfn); *ns_size = ndctl_pfn_get_size(pfn); return 0; } if (dax) { *ns_offset = ndctl_dax_get_resource(dax); *ns_size = ndctl_dax_get_size(dax); return 0; } if (btt) return -EOPNOTSUPP; /* raw */ *ns_offset = ndctl_namespace_get_resource(ndns); *ns_size = ndctl_namespace_get_size(ndns); return 0; } static int block_to_spa_offset(struct ndctl_namespace *ndns, unsigned long long block, unsigned long long count, u64 *offset, u64 *length) { struct ndctl_ctx *ctx = ndctl_namespace_get_ctx(ndns); unsigned long long ns_offset, ns_size; int rc; rc = ndctl_namespace_get_injection_bounds(ndns, &ns_offset, &ns_size); if (rc) return rc; *offset = ns_offset + block * 512; *length = count * 512; /* check bounds */ if (*offset + *length > ns_offset + ns_size) { dbg(ctx, "Error: block %#llx, count %#llx are out of bounds\n", block, count); return -EINVAL; } return 0; } static int translate_status(u32 status) { switch (status) { case ND_ARS_ERR_INJ_STATUS_NOT_SUPP: return -EOPNOTSUPP; case ND_ARS_ERR_INJ_STATUS_INVALID_PARAM: return -EINVAL; } return 0; } static int ndctl_namespace_get_clear_unit(struct ndctl_namespace *ndns) { struct ndctl_bus *bus = ndctl_namespace_get_bus(ndns); struct ndctl_ctx *ctx = ndctl_bus_get_ctx(bus); unsigned long long ns_offset, ns_size; unsigned int clear_unit; struct ndctl_cmd *cmd; int rc; rc = ndctl_namespace_get_injection_bounds(ndns, &ns_offset, &ns_size); if (rc) return rc; cmd = ndctl_bus_cmd_new_ars_cap(bus, ns_offset, ns_size); if (!cmd) { err(ctx, "%s: failed to create cmd\n", ndctl_namespace_get_devname(ndns)); return -ENOTTY; } rc = ndctl_cmd_submit(cmd); if (rc < 0) { dbg(ctx, "Error submitting ars_cap: %d\n", rc); goto out; } clear_unit = ndctl_cmd_ars_cap_get_clear_unit(cmd); if (clear_unit == 0) { dbg(ctx, "Got an invalid clear_err_unit from ars_cap\n"); rc = -EINVAL; goto out; } rc = clear_unit; out: ndctl_cmd_unref(cmd); return rc; } static int ndctl_namespace_inject_one_error(struct ndctl_namespace *ndns, unsigned long long block, unsigned int flags) { struct ndctl_bus *bus = ndctl_namespace_get_bus(ndns); struct ndctl_ctx *ctx = ndctl_bus_get_ctx(bus); struct nd_cmd_ars_err_inj *err_inj; struct nd_cmd_pkg *pkg; struct ndctl_cmd *cmd; u64 offset, length; int rc, clear_unit; rc = block_to_spa_offset(ndns, block, 1, &offset, &length); if (rc) return rc; clear_unit = ndctl_namespace_get_clear_unit(ndns); if (clear_unit < 0) return clear_unit; if (!(flags & (1 << NDCTL_NS_INJECT_SATURATE))) { /* clamp injection length per block to the clear_unit */ if (length > (unsigned int)clear_unit) length = clear_unit; } cmd = ndctl_bus_cmd_new_err_inj(bus); if (!cmd) return -ENOMEM; pkg = (struct nd_cmd_pkg *)&cmd->cmd_buf[0]; err_inj = (struct nd_cmd_ars_err_inj *)&pkg->nd_payload[0]; err_inj->err_inj_spa_range_base = offset; err_inj->err_inj_spa_range_length = length; if (flags & (1 << NDCTL_NS_INJECT_NOTIFY)) err_inj->err_inj_options |= (1 << ND_ARS_ERR_INJ_OPT_NOTIFY); rc = ndctl_cmd_submit(cmd); if (rc < 0) { dbg(ctx, "Error submitting command: %d\n", rc); goto out; } rc = translate_status(err_inj->status); out: ndctl_cmd_unref(cmd); return rc; } NDCTL_EXPORT int ndctl_namespace_inject_error2(struct ndctl_namespace *ndns, unsigned long long block, unsigned long long count, unsigned int flags) { struct ndctl_bus *bus = ndctl_namespace_get_bus(ndns); struct ndctl_ctx *ctx = ndctl_bus_get_ctx(bus); unsigned long long i; int rc = -EINVAL; if (!ndctl_bus_has_error_injection(bus)) return -EOPNOTSUPP; if (!ndctl_bus_has_nfit(bus)) return -EOPNOTSUPP; for (i = 0; i < count; i++) { rc = ndctl_namespace_inject_one_error(ndns, block + i, flags); if (rc) { err(ctx, "Injection failed at block %llx\n", block + i); return rc; } } return rc; } NDCTL_EXPORT int ndctl_namespace_inject_error(struct ndctl_namespace *ndns, unsigned long long block, unsigned long long count, bool notify) { return ndctl_namespace_inject_error2(ndns, block, count, notify ? (1 << NDCTL_NS_INJECT_NOTIFY) : 0); } static int ndctl_namespace_uninject_one_error(struct ndctl_namespace *ndns, unsigned long long block, unsigned int flags) { struct ndctl_bus *bus = ndctl_namespace_get_bus(ndns); struct ndctl_ctx *ctx = ndctl_bus_get_ctx(bus); struct nd_cmd_ars_err_inj_clr *err_inj_clr; struct nd_cmd_pkg *pkg; struct ndctl_cmd *cmd; u64 offset, length; int rc, clear_unit; rc = block_to_spa_offset(ndns, block, 1, &offset, &length); if (rc) return rc; clear_unit = ndctl_namespace_get_clear_unit(ndns); if (clear_unit < 0) return clear_unit; if (!(flags & (1 << NDCTL_NS_INJECT_SATURATE))) { /* clamp injection length per block to the clear_unit */ if (length > (unsigned int)clear_unit) length = clear_unit; } cmd = ndctl_bus_cmd_new_err_inj_clr(bus); if (!cmd) return -ENOMEM; pkg = (struct nd_cmd_pkg *)&cmd->cmd_buf[0]; err_inj_clr = (struct nd_cmd_ars_err_inj_clr *)&pkg->nd_payload[0]; err_inj_clr->err_inj_clr_spa_range_base = offset; err_inj_clr->err_inj_clr_spa_range_length = length; rc = ndctl_cmd_submit(cmd); if (rc < 0) { dbg(ctx, "Error submitting command: %d\n", rc); goto out; } rc = translate_status(err_inj_clr->status); out: ndctl_cmd_unref(cmd); return rc; } NDCTL_EXPORT int ndctl_namespace_uninject_error2(struct ndctl_namespace *ndns, unsigned long long block, unsigned long long count, unsigned int flags) { struct ndctl_bus *bus = ndctl_namespace_get_bus(ndns); struct ndctl_ctx *ctx = ndctl_bus_get_ctx(bus); unsigned long long i; int rc = -EINVAL; if (!ndctl_bus_has_error_injection(bus)) return -EOPNOTSUPP; if (!ndctl_bus_has_nfit(bus)) return -EOPNOTSUPP; for (i = 0; i < count; i++) { rc = ndctl_namespace_uninject_one_error(ndns, block + i, flags); if (rc) { err(ctx, "Un-injection failed at block %llx\n", block + i); return rc; } } return rc; } NDCTL_EXPORT int ndctl_namespace_uninject_error(struct ndctl_namespace *ndns, unsigned long long block, unsigned long long count) { return ndctl_namespace_uninject_error2(ndns, block, count, 0); } static int bb_add_record(struct list_head *h, u64 block, u64 count) { struct ndctl_bb *bb, *bb_iter, *bb_next, *bb_prev; int merged = 0; bb = calloc(1, sizeof(*bb)); if (bb == NULL) return -ENOMEM; bb->block = block; bb->count = count; if (list_empty(h)) { list_add(h, &bb->list); return 0; } /* add 'bb' to the list such that it remains sorted */ list_for_each(h, bb_iter, list) { /* Find insertion point */ bb_prev = list_prev(h, bb_iter, list); bb_next = list_next(h, bb_iter, list); if (bb_prev == NULL) { /* bb_iter is the first entry */ if (bb->block < bb_iter->block) { list_add(h, &bb->list); bb = NULL; break; } } if (bb_next == NULL) { /* * bb_iter is the last entry. If we've reached here, * the only option is to add to the tail as the case * for "tail - 1" should have been covered by the * following checks for the previous iteration. */ list_add_tail(h, &bb->list); bb = NULL; break; } /* Add to the left of bb_iter */ if (bb->block <= bb_iter->block) { if (bb_prev && (bb_prev->block <= bb->block)) { list_add_after(h, &bb_prev->list, &bb->list); bb = NULL; break; } } /* Add to the right of bb_iter */ if (bb_iter->block <= bb->block) { if (bb_next && (bb->block <= bb_next->block)) { list_add_after(h, &bb_iter->list, &bb->list); bb = NULL; break; } } } /* ensure bb has actually been consumed (set to NULL earlier) */ if (bb != NULL) { free(bb); return -ENXIO; } /* second pass over the list looking for mergeable entries */ list_for_each(h, bb_iter, list) { u64 cur_end, next_end, cur_start, next_start; /* * test for merges in a loop here because one addition can * potentially have a cascading merge effect on multiple * remaining entries */ do { /* reset the merged flag */ merged = 0; bb_next = list_next(h, bb_iter, list); if (bb_next == NULL) break; cur_start = bb_iter->block; next_start = bb_next->block; cur_end = bb_iter->block + bb_iter->count - 1; next_end = bb_next->block + bb_next->count - 1; if (cur_end >= next_start) { /* overlapping records that can be merged */ if (next_end > cur_end) { /* next extends cur */ bb_iter->count = next_end - cur_start + 1; } else { /* next is contained in cur */ ; } /* next is now redundant */ list_del_from(h, &bb_next->list); free(bb_next); merged = 1; continue; } if (next_start == cur_end + 1) { /* adjoining records that can be merged */ bb_iter->count = next_end - cur_start + 1; list_del_from(h, &bb_next->list); free(bb_next); merged = 1; continue; } } while (merged); } return 0; } static int injection_status_to_bb(struct ndctl_namespace *ndns, struct nd_cmd_ars_err_inj_stat *stat, u64 ns_spa, u64 ns_size) { unsigned int i; int rc = 0; for (i = 0; i < stat->inj_err_rec_count; i++) { u64 ns_off, rec_off, rec_len; u64 block, count, start_pad; rec_off = stat->record[i].err_inj_stat_spa_range_base; rec_len = stat->record[i].err_inj_stat_spa_range_length; /* discard ranges outside the provided namespace */ if (rec_off < ns_spa) continue; if (rec_off >= ns_spa + ns_size) continue; /* translate spa offset to namespace offset */ ns_off = rec_off - ns_spa; block = ALIGN_DOWN(ns_off, 512)/512; start_pad = ns_off - (block * 512); count = ALIGN(start_pad + rec_len, 512)/512; rc = bb_add_record(&ndns->injected_bb, block, count); if (rc) break; } return rc; } NDCTL_EXPORT int ndctl_namespace_injection_status(struct ndctl_namespace *ndns) { struct ndctl_bus *bus = ndctl_namespace_get_bus(ndns); struct ndctl_ctx *ctx = ndctl_bus_get_ctx(bus); struct nd_cmd_ars_err_inj_stat *err_inj_stat; unsigned long long ns_offset, ns_size; int rc = -EOPNOTSUPP, buf_size; struct ndctl_cmd *cmd = NULL; struct nd_cmd_pkg *pkg; if (!ndctl_bus_has_error_injection(bus)) return -EOPNOTSUPP; if (ndctl_bus_has_nfit(bus)) { rc = ndctl_namespace_get_injection_bounds(ndns, &ns_offset, &ns_size); if (rc) return rc; cmd = ndctl_bus_cmd_new_ars_cap(bus, ns_offset, ns_size); if (!cmd) { err(ctx, "%s: failed to create cmd\n", ndctl_namespace_get_devname(ndns)); return -ENOTTY; } rc = ndctl_cmd_submit(cmd); if (rc < 0) { dbg(ctx, "Error submitting ars_cap: %d\n", rc); goto out; } buf_size = ndctl_cmd_ars_cap_get_size(cmd); if (buf_size == 0) { dbg(ctx, "Got an invalid max_ars_out from ars_cap\n"); rc = -EINVAL; goto out; } ndctl_cmd_unref(cmd); cmd = ndctl_bus_cmd_new_err_inj_stat(bus, buf_size); if (!cmd) return -ENOMEM; pkg = (struct nd_cmd_pkg *)&cmd->cmd_buf[0]; err_inj_stat = (struct nd_cmd_ars_err_inj_stat *)&pkg->nd_payload[0]; rc = ndctl_cmd_submit(cmd); if (rc < 0) { dbg(ctx, "Error submitting command: %d\n", rc); goto out; } rc = injection_status_to_bb(ndns, err_inj_stat, ns_offset, ns_size); if (rc) { dbg(ctx, "Error converting status to badblocks: %d\n", rc); goto out; } } out: ndctl_cmd_unref(cmd); return rc; } NDCTL_EXPORT struct ndctl_bb *ndctl_namespace_injection_get_first_bb( struct ndctl_namespace *ndns) { return list_top(&ndns->injected_bb, struct ndctl_bb, list); } NDCTL_EXPORT struct ndctl_bb *ndctl_namespace_injection_get_next_bb( struct ndctl_namespace *ndns, struct ndctl_bb *bb) { return list_next(&ndns->injected_bb, bb, list); } NDCTL_EXPORT unsigned long long ndctl_bb_get_block(struct ndctl_bb *bb) { if (bb) return bb->block; errno = EINVAL; return ULLONG_MAX; } NDCTL_EXPORT unsigned long long ndctl_bb_get_count(struct ndctl_bb *bb) { if (bb) return bb->count; errno = EINVAL; return ULLONG_MAX; } ndctl-81/ndctl/lib/intel.c000066400000000000000000000562111476737544500155740ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-2.1 // Copyright (C) 2016-2020, Intel Corporation. All rights reserved. #include #include #include #include #include "private.h" static unsigned int intel_cmd_get_firmware_status(struct ndctl_cmd *cmd) { struct nd_pkg_intel *intel = cmd->intel; switch (intel->gen.nd_command) { case ND_INTEL_SMART: return intel->smart.status; case ND_INTEL_SMART_THRESHOLD: return intel->thresh.status; case ND_INTEL_SMART_SET_THRESHOLD: return intel->set_thresh.status; case ND_INTEL_SMART_INJECT: return intel->inject.status; case ND_INTEL_FW_GET_INFO: return intel->info.status; case ND_INTEL_FW_START_UPDATE: return intel->start.status; case ND_INTEL_FW_SEND_DATA: { struct nd_intel_fw_send_data *send = &intel->send; u32 status; /* the last dword after the payload is reserved for status */ memcpy(&status, ((void *) send) + sizeof(*send) + send->length, sizeof(status)); return status; } case ND_INTEL_FW_FINISH_UPDATE: return intel->finish.status; case ND_INTEL_FW_FINISH_STATUS_QUERY: return intel->fquery.status; case ND_INTEL_ENABLE_LSS_STATUS: return intel->lss.status; } return -1U; } static int intel_cmd_xlat_firmware_status(struct ndctl_cmd *cmd) { struct nd_pkg_intel *pkg = cmd->intel; unsigned int status, ext_status; status = cmd->get_firmware_status(cmd) & ND_INTEL_STATUS_MASK; ext_status = cmd->get_firmware_status(cmd) & ND_INTEL_STATUS_EXTEND_MASK; /* Common statuses */ switch (status) { case ND_INTEL_STATUS_SUCCESS: return 0; case ND_INTEL_STATUS_NOTSUPP: return -EOPNOTSUPP; case ND_INTEL_STATUS_NOTEXIST: return -ENXIO; case ND_INTEL_STATUS_INVALPARM: return -EINVAL; case ND_INTEL_STATUS_HWERR: return -EIO; case ND_INTEL_STATUS_RETRY: return -EAGAIN; case ND_INTEL_STATUS_EXTEND: /* refer to extended status, break out of this */ break; case ND_INTEL_STATUS_NORES: return -EAGAIN; case ND_INTEL_STATUS_NOTREADY: return -EBUSY; } /* Extended status is command specific */ switch (pkg->gen.nd_command) { case ND_INTEL_SMART: case ND_INTEL_SMART_THRESHOLD: case ND_INTEL_SMART_SET_THRESHOLD: /* ext status not specified */ break; case ND_INTEL_SMART_INJECT: /* smart injection not enabled */ if (ext_status == ND_INTEL_STATUS_INJ_DISABLED) return -ENXIO; break; } return -ENOMSG; } static struct ndctl_cmd *alloc_intel_cmd(struct ndctl_dimm *dimm, unsigned func, size_t in_size, size_t out_size) { struct ndctl_ctx *ctx = ndctl_dimm_get_ctx(dimm); struct ndctl_cmd *cmd; size_t size; if (!ndctl_dimm_is_cmd_supported(dimm, ND_CMD_CALL)) { dbg(ctx, "unsupported cmd: %d\n", ND_CMD_CALL); return NULL; } if (test_dimm_dsm(dimm, func) == DIMM_DSM_UNSUPPORTED) { dbg(ctx, "unsupported function: %d\n", func); return NULL; } size = sizeof(*cmd) + sizeof(struct nd_pkg_intel) + in_size + out_size; cmd = calloc(1, size); if (!cmd) return NULL; cmd->dimm = dimm; ndctl_cmd_ref(cmd); cmd->type = ND_CMD_CALL; cmd->size = size; cmd->status = 1; cmd->get_firmware_status = intel_cmd_get_firmware_status; *(cmd->intel) = (struct nd_pkg_intel) { .gen = { .nd_family = NVDIMM_FAMILY_INTEL, .nd_command = func, .nd_size_in = in_size, .nd_size_out = out_size, }, }; return cmd; } static struct ndctl_cmd *intel_dimm_cmd_new_smart(struct ndctl_dimm *dimm) { struct ndctl_cmd *cmd; BUILD_ASSERT(sizeof(struct nd_intel_smart) == 132); cmd = alloc_intel_cmd(dimm, ND_INTEL_SMART, 0, sizeof(cmd->intel->smart)); if (!cmd) return NULL; return cmd; } static int intel_smart_valid(struct ndctl_cmd *cmd) { struct nd_pkg_intel *pkg = cmd->intel; if (cmd->type != ND_CMD_CALL || cmd->status != 0 || pkg->gen.nd_family != NVDIMM_FAMILY_INTEL || pkg->gen.nd_command != ND_INTEL_SMART) return cmd->status < 0 ? cmd->status : -EINVAL; return 0; } #define intel_smart_get_field(cmd, field) \ static unsigned int intel_cmd_smart_get_##field(struct ndctl_cmd *cmd) \ { \ int rc; \ rc = intel_smart_valid(cmd); \ if (rc < 0) { \ errno = -rc; \ return UINT_MAX; \ } \ return cmd->intel->smart.field; \ } static unsigned int intel_cmd_smart_get_flags(struct ndctl_cmd *cmd) { unsigned int flags = 0; unsigned int intel_flags; if (intel_smart_valid(cmd) < 0) return 0; /* translate intel specific flags to libndctl api smart flags */ intel_flags = cmd->intel->smart.flags; if (intel_flags & ND_INTEL_SMART_HEALTH_VALID) flags |= ND_SMART_HEALTH_VALID; if (intel_flags & ND_INTEL_SMART_SPARES_VALID) flags |= ND_SMART_SPARES_VALID; if (intel_flags & ND_INTEL_SMART_USED_VALID) flags |= ND_SMART_USED_VALID; if (intel_flags & ND_INTEL_SMART_MTEMP_VALID) flags |= ND_SMART_MTEMP_VALID; if (intel_flags & ND_INTEL_SMART_CTEMP_VALID) flags |= ND_SMART_CTEMP_VALID; if (intel_flags & ND_INTEL_SMART_SHUTDOWN_COUNT_VALID) flags |= ND_SMART_SHUTDOWN_COUNT_VALID; if (intel_flags & ND_INTEL_SMART_AIT_STATUS_VALID) flags |= ND_SMART_AIT_STATUS_VALID; if (intel_flags & ND_INTEL_SMART_PTEMP_VALID) flags |= ND_SMART_PTEMP_VALID; if (intel_flags & ND_INTEL_SMART_ALARM_VALID) flags |= ND_SMART_ALARM_VALID; if (intel_flags & ND_INTEL_SMART_SHUTDOWN_VALID) flags |= ND_SMART_SHUTDOWN_VALID; if (intel_flags & ND_INTEL_SMART_VENDOR_VALID) flags |= ND_SMART_VENDOR_VALID; return flags; } static unsigned int intel_cmd_smart_get_health(struct ndctl_cmd *cmd) { unsigned int health = 0; unsigned int intel_health; if (intel_smart_valid(cmd) < 0) return 0; intel_health = cmd->intel->smart.health; if (intel_health & ND_INTEL_SMART_NON_CRITICAL_HEALTH) health |= ND_SMART_NON_CRITICAL_HEALTH; if (intel_health & ND_INTEL_SMART_CRITICAL_HEALTH) health |= ND_SMART_CRITICAL_HEALTH; if (intel_health & ND_INTEL_SMART_FATAL_HEALTH) health |= ND_SMART_FATAL_HEALTH; return health; } intel_smart_get_field(cmd, media_temperature) intel_smart_get_field(cmd, ctrl_temperature) intel_smart_get_field(cmd, spares) intel_smart_get_field(cmd, alarm_flags) intel_smart_get_field(cmd, life_used) intel_smart_get_field(cmd, shutdown_state) intel_smart_get_field(cmd, shutdown_count) intel_smart_get_field(cmd, vendor_size) static unsigned char *intel_cmd_smart_get_vendor_data(struct ndctl_cmd *cmd) { if (intel_smart_valid(cmd) < 0) return NULL; return cmd->intel->smart.vendor_data; } static int intel_smart_threshold_valid(struct ndctl_cmd *cmd) { struct nd_pkg_intel *pkg = cmd->intel; if (cmd->type != ND_CMD_CALL || cmd->status != 0 || pkg->gen.nd_family != NVDIMM_FAMILY_INTEL || pkg->gen.nd_command != ND_INTEL_SMART_THRESHOLD) return cmd->status < 0 ? cmd->status : -EINVAL; return 0; } #define intel_smart_threshold_get_field(cmd, field) \ static unsigned int intel_cmd_smart_threshold_get_##field( \ struct ndctl_cmd *cmd) \ { \ int rc; \ rc = intel_smart_threshold_valid(cmd); \ if (rc < 0) { \ errno = -rc; \ return UINT_MAX; \ } \ return cmd->intel->thresh.field; \ } static unsigned int intel_cmd_smart_threshold_get_alarm_control( struct ndctl_cmd *cmd) { struct nd_intel_smart_threshold *thresh; unsigned int flags = 0; if (intel_smart_threshold_valid(cmd) < 0) return 0; thresh = &cmd->intel->thresh; if (thresh->alarm_control & ND_INTEL_SMART_SPARE_TRIP) flags |= ND_SMART_SPARE_TRIP; if (thresh->alarm_control & ND_INTEL_SMART_TEMP_TRIP) flags |= ND_SMART_TEMP_TRIP; if (thresh->alarm_control & ND_INTEL_SMART_CTEMP_TRIP) flags |= ND_SMART_CTEMP_TRIP; return flags; } intel_smart_threshold_get_field(cmd, media_temperature) intel_smart_threshold_get_field(cmd, ctrl_temperature) intel_smart_threshold_get_field(cmd, spares) static struct ndctl_cmd *intel_dimm_cmd_new_smart_threshold( struct ndctl_dimm *dimm) { struct ndctl_cmd *cmd; BUILD_ASSERT(sizeof(struct nd_intel_smart_threshold) == 12); cmd = alloc_intel_cmd(dimm, ND_INTEL_SMART_THRESHOLD, 0, sizeof(cmd->intel->thresh)); if (!cmd) return NULL; return cmd; } static struct ndctl_cmd *intel_dimm_cmd_new_smart_set_threshold( struct ndctl_cmd *cmd_thresh) { struct ndctl_cmd *cmd; struct nd_intel_smart_threshold *thresh; struct nd_intel_smart_set_threshold *set_thresh; BUILD_ASSERT(sizeof(struct nd_intel_smart_set_threshold) == 11); if (intel_smart_threshold_valid(cmd_thresh) < 0) return NULL; cmd = alloc_intel_cmd(cmd_thresh->dimm, ND_INTEL_SMART_SET_THRESHOLD, offsetof(typeof(*set_thresh), status), 4); if (!cmd) return NULL; cmd->source = cmd_thresh; ndctl_cmd_ref(cmd_thresh); set_thresh = &cmd->intel->set_thresh; thresh = &cmd_thresh->intel->thresh; set_thresh->alarm_control = thresh->alarm_control; set_thresh->spares = thresh->spares; set_thresh->media_temperature = thresh->media_temperature; set_thresh->ctrl_temperature = thresh->ctrl_temperature; return cmd; } static int intel_smart_set_threshold_valid(struct ndctl_cmd *cmd) { struct nd_pkg_intel *pkg = cmd->intel; if (cmd->type != ND_CMD_CALL || cmd->status != 1 || pkg->gen.nd_family != NVDIMM_FAMILY_INTEL || pkg->gen.nd_command != ND_INTEL_SMART_SET_THRESHOLD) return -EINVAL; return 0; } #define intel_smart_set_threshold_field(field) \ static int intel_cmd_smart_threshold_set_##field( \ struct ndctl_cmd *cmd, unsigned int val) \ { \ if (intel_smart_set_threshold_valid(cmd) < 0) \ return -EINVAL; \ cmd->intel->set_thresh.field = val; \ return 0; \ } static unsigned int intel_cmd_smart_threshold_get_supported_alarms( struct ndctl_cmd *cmd) { if (intel_smart_set_threshold_valid(cmd) < 0) return 0; return ND_SMART_SPARE_TRIP | ND_SMART_MTEMP_TRIP | ND_SMART_CTEMP_TRIP; } intel_smart_set_threshold_field(alarm_control) intel_smart_set_threshold_field(spares) intel_smart_set_threshold_field(media_temperature) intel_smart_set_threshold_field(ctrl_temperature) static int intel_smart_inject_valid(struct ndctl_cmd *cmd) { struct nd_pkg_intel *pkg = cmd->intel; if (cmd->type != ND_CMD_CALL || cmd->status != 1 || pkg->gen.nd_family != NVDIMM_FAMILY_INTEL || pkg->gen.nd_command != ND_INTEL_SMART_INJECT) return cmd->status < 0 ? cmd->status : -EINVAL; return 0; } static struct ndctl_cmd *intel_new_smart_inject(struct ndctl_dimm *dimm) { struct ndctl_cmd *cmd; BUILD_ASSERT(sizeof(struct nd_intel_smart_inject) == 19); cmd = alloc_intel_cmd(dimm, ND_INTEL_SMART_INJECT, offsetof(struct nd_intel_smart_inject, status), 4); if (!cmd) return NULL; return cmd; } static int intel_cmd_smart_inject_media_temperature(struct ndctl_cmd *cmd, bool enable, unsigned int mtemp) { struct nd_intel_smart_inject *inj; if (intel_smart_inject_valid(cmd) < 0) return -EINVAL; inj = &cmd->intel->inject; inj->flags |= ND_INTEL_SMART_INJECT_MTEMP; inj->mtemp_enable = enable == true; inj->media_temperature = mtemp; return 0; } static int intel_cmd_smart_inject_spares(struct ndctl_cmd *cmd, bool enable, unsigned int spares) { struct nd_intel_smart_inject *inj; if (intel_smart_inject_valid(cmd) < 0) return -EINVAL; inj = &cmd->intel->inject; inj->flags |= ND_INTEL_SMART_INJECT_SPARE; inj->spare_enable = enable == true; inj->spares = spares; return 0; } static int intel_cmd_smart_inject_fatal(struct ndctl_cmd *cmd, bool enable) { struct nd_intel_smart_inject *inj; if (intel_smart_inject_valid(cmd) < 0) return -EINVAL; inj = &cmd->intel->inject; inj->flags |= ND_INTEL_SMART_INJECT_FATAL; inj->fatal_enable = enable == true; return 0; } static int intel_cmd_smart_inject_unsafe_shutdown(struct ndctl_cmd *cmd, bool enable) { struct nd_intel_smart_inject *inj; if (intel_smart_inject_valid(cmd) < 0) return -EINVAL; inj = &cmd->intel->inject; inj->flags |= ND_INTEL_SMART_INJECT_SHUTDOWN; inj->unsafe_shutdown_enable = enable == true; return 0; } static int intel_dimm_smart_inject_supported(struct ndctl_dimm *dimm) { struct ndctl_ctx *ctx = ndctl_dimm_get_ctx(dimm); if (!ndctl_dimm_is_cmd_supported(dimm, ND_CMD_CALL)) { dbg(ctx, "unsupported cmd: %d\n", ND_CMD_CALL); return -EOPNOTSUPP; } if (!test_dimm_dsm(dimm, ND_INTEL_SMART_INJECT)) { dbg(ctx, "smart injection functions unsupported\n"); return -EIO; } /* Indicate all smart injection types are supported */ return ND_SMART_INJECT_SPARES_REMAINING | ND_SMART_INJECT_MEDIA_TEMPERATURE | ND_SMART_INJECT_CTRL_TEMPERATURE | ND_SMART_INJECT_HEALTH_STATE | ND_SMART_INJECT_UNCLEAN_SHUTDOWN; } static const char *intel_cmd_desc(int fn) { static const char *descs[] = { [ND_INTEL_SMART] = "smart", [ND_INTEL_SMART_INJECT] = "smart_inject", [ND_INTEL_SMART_THRESHOLD] = "smart_thresh", [ND_INTEL_FW_GET_INFO] = "firmware_get_info", [ND_INTEL_FW_START_UPDATE] = "firmware_start_update", [ND_INTEL_FW_SEND_DATA] = "firmware_send_data", [ND_INTEL_FW_FINISH_UPDATE] = "firmware_finish_update", [ND_INTEL_FW_FINISH_STATUS_QUERY] = "firmware_finish_query", [ND_INTEL_SMART_SET_THRESHOLD] = "smart_set_thresh", }; const char *desc = descs[fn]; if (fn >= (int) ARRAY_SIZE(descs)) return "unknown"; if (!desc) return "unknown"; return desc; } static struct ndctl_cmd *intel_dimm_cmd_new_fw_get_info(struct ndctl_dimm *dimm) { struct ndctl_cmd *cmd; BUILD_ASSERT(sizeof(struct nd_intel_fw_info) == 44); cmd = alloc_intel_cmd(dimm, ND_INTEL_FW_GET_INFO, 0, sizeof(cmd->intel->info)); if (!cmd) return NULL; return cmd; } static int intel_fw_get_info_valid(struct ndctl_cmd *cmd) { struct nd_pkg_intel *pkg = cmd->intel; if (cmd->type != ND_CMD_CALL || cmd->status != 0 || pkg->gen.nd_family != NVDIMM_FAMILY_INTEL || pkg->gen.nd_command != ND_INTEL_FW_GET_INFO) return -EINVAL; return 0; } #define intel_fw_info_get_field32(cmd, field) \ static unsigned int intel_cmd_fw_info_get_##field( \ struct ndctl_cmd *cmd) \ { \ int rc; \ rc = intel_fw_get_info_valid(cmd); \ if (rc < 0) { \ errno = -rc; \ return UINT_MAX; \ } \ return cmd->intel->info.field; \ } #define intel_fw_info_get_field64(cmd, field) \ static unsigned long long intel_cmd_fw_info_get_##field( \ struct ndctl_cmd *cmd) \ { \ int rc; \ rc = intel_fw_get_info_valid(cmd); \ if (rc < 0) { \ errno = -rc; \ return ULLONG_MAX; \ } \ return cmd->intel->info.field; \ } intel_fw_info_get_field32(cmd, storage_size) intel_fw_info_get_field32(cmd, max_send_len) intel_fw_info_get_field32(cmd, query_interval) intel_fw_info_get_field32(cmd, max_query_time); intel_fw_info_get_field64(cmd, run_version); static unsigned long long intel_cmd_fw_info_get_updated_version( struct ndctl_cmd *cmd) { int rc; rc = intel_fw_get_info_valid(cmd); if (rc < 0) { errno = -rc; return ULLONG_MAX; } return cmd->intel->info.updated_version; } static struct ndctl_cmd *intel_dimm_cmd_new_fw_start(struct ndctl_dimm *dimm) { struct ndctl_cmd *cmd; BUILD_ASSERT(sizeof(struct nd_intel_fw_start) == 8); cmd = alloc_intel_cmd(dimm, ND_INTEL_FW_START_UPDATE, 0, sizeof(cmd->intel->start)); if (!cmd) return NULL; return cmd; } static int intel_fw_start_valid(struct ndctl_cmd *cmd) { struct nd_pkg_intel *pkg = cmd->intel; if (cmd->type != ND_CMD_CALL || cmd->status != 0 || pkg->gen.nd_family != NVDIMM_FAMILY_INTEL || pkg->gen.nd_command != ND_INTEL_FW_START_UPDATE) return -EINVAL; return 0; } static unsigned int intel_cmd_fw_start_get_context(struct ndctl_cmd *cmd) { int rc; rc = intel_fw_start_valid(cmd); if (rc < 0) { errno = -rc; return UINT_MAX; } return cmd->intel->start.context; } static struct ndctl_cmd *intel_dimm_cmd_new_fw_send(struct ndctl_cmd *start, unsigned int offset, unsigned int len, void *data) { struct ndctl_cmd *cmd; BUILD_ASSERT(sizeof(struct nd_intel_fw_send_data) == 12); cmd = alloc_intel_cmd(start->dimm, ND_INTEL_FW_SEND_DATA, sizeof(cmd->intel->send) + len, 4); if (!cmd) return NULL; cmd->intel->send.context = start->intel->start.context; cmd->intel->send.offset = offset; cmd->intel->send.length = len; memcpy(cmd->intel->send.data, data, len); return cmd; } static struct ndctl_cmd *intel_dimm_cmd_new_fw_finish(struct ndctl_cmd *start) { struct ndctl_cmd *cmd; BUILD_ASSERT(sizeof(struct nd_intel_fw_finish_update) == 12); cmd = alloc_intel_cmd(start->dimm, ND_INTEL_FW_FINISH_UPDATE, offsetof(struct nd_intel_fw_finish_update, status), 4); if (!cmd) return NULL; cmd->intel->finish.context = start->intel->start.context; cmd->intel->finish.ctrl_flags = 0; return cmd; } static struct ndctl_cmd *intel_dimm_cmd_new_fw_abort(struct ndctl_cmd *start) { struct ndctl_cmd *cmd; BUILD_ASSERT(sizeof(struct nd_intel_fw_finish_update) == 12); cmd = alloc_intel_cmd(start->dimm, ND_INTEL_FW_FINISH_UPDATE, sizeof(cmd->intel->finish) - 4, 4); if (!cmd) return NULL; cmd->intel->finish.context = start->intel->start.context; cmd->intel->finish.ctrl_flags = 1; return cmd; } static struct ndctl_cmd * intel_dimm_cmd_new_fw_finish_query(struct ndctl_cmd *start) { struct ndctl_cmd *cmd; BUILD_ASSERT(sizeof(struct nd_intel_fw_finish_query) == 16); cmd = alloc_intel_cmd(start->dimm, ND_INTEL_FW_FINISH_STATUS_QUERY, 4, sizeof(cmd->intel->fquery) - 4); if (!cmd) return NULL; cmd->intel->fquery.context = start->intel->start.context; return cmd; } static int intel_fw_fquery_valid(struct ndctl_cmd *cmd) { struct nd_pkg_intel *pkg = cmd->intel; if (cmd->type != ND_CMD_CALL || cmd->status != 0 || pkg->gen.nd_family != NVDIMM_FAMILY_INTEL || pkg->gen.nd_command != ND_INTEL_FW_FINISH_STATUS_QUERY) return -EINVAL; return 0; } static unsigned long long intel_cmd_fw_fquery_get_fw_rev(struct ndctl_cmd *cmd) { int rc; rc = intel_fw_fquery_valid(cmd); if (rc < 0) { errno = -rc; return ULLONG_MAX; } return cmd->intel->fquery.updated_fw_rev; } static enum ND_FW_STATUS intel_cmd_fw_xlat_extend_firmware_status(struct ndctl_cmd *cmd, unsigned int status) { /* * Note: the cases commented out are identical to the ones that are * not. They are there for reference. */ switch (status & ND_INTEL_STATUS_EXTEND_MASK) { case ND_INTEL_STATUS_START_BUSY: /* case ND_INTEL_STATUS_SEND_CTXINVAL: */ /* case ND_INTEL_STATUS_FIN_CTXINVAL: */ /* case ND_INTEL_STATUS_FQ_CTXINVAL: */ if (cmd->intel->gen.nd_command == ND_INTEL_FW_START_UPDATE) return FW_EBUSY; else return FW_EINVAL_CTX; case ND_INTEL_STATUS_FIN_DONE: /* case ND_INTEL_STATUS_FQ_BUSY: */ if (cmd->intel->gen.nd_command == ND_INTEL_FW_FINISH_UPDATE) return FW_ALREADY_DONE; else return FW_EBUSY; case ND_INTEL_STATUS_FIN_BAD: /* case ND_INTEL_STATUS_FQ_BAD: */ return FW_EBADFW; case ND_INTEL_STATUS_FIN_ABORTED: /* case ND_INTEL_STATUS_FQ_ORDER: */ if (cmd->intel->gen.nd_command == ND_INTEL_FW_FINISH_UPDATE) return FW_ABORTED; else return FW_ESEQUENCE; } return FW_EUNKNOWN; } static enum ND_FW_STATUS intel_cmd_fw_xlat_firmware_status(struct ndctl_cmd *cmd) { unsigned int status = intel_cmd_get_firmware_status(cmd); switch (status & ND_INTEL_STATUS_MASK) { case ND_INTEL_STATUS_SUCCESS: return FW_SUCCESS; case ND_INTEL_STATUS_NOTSUPP: return FW_ENOTSUPP; case ND_INTEL_STATUS_NOTEXIST: return FW_ENOTEXIST; case ND_INTEL_STATUS_INVALPARM: return FW_EINVAL; case ND_INTEL_STATUS_HWERR: return FW_EHWERR; case ND_INTEL_STATUS_RETRY: return FW_ERETRY; case ND_INTEL_STATUS_EXTEND: return intel_cmd_fw_xlat_extend_firmware_status(cmd, status); case ND_INTEL_STATUS_NORES: return FW_ENORES; case ND_INTEL_STATUS_NOTREADY: return FW_ENOTREADY; } return FW_EUNKNOWN; } static struct ndctl_cmd * intel_dimm_cmd_new_lss(struct ndctl_dimm *dimm) { struct ndctl_cmd *cmd; BUILD_ASSERT(sizeof(struct nd_intel_lss) == 5); cmd = alloc_intel_cmd(dimm, ND_INTEL_ENABLE_LSS_STATUS, 1, 4); if (!cmd) return NULL; cmd->intel->lss.enable = 1; return cmd; } static int intel_dimm_fw_update_supported(struct ndctl_dimm *dimm) { struct ndctl_ctx *ctx = ndctl_dimm_get_ctx(dimm); if (!ndctl_dimm_is_cmd_supported(dimm, ND_CMD_CALL)) { dbg(ctx, "unsupported cmd: %d\n", ND_CMD_CALL); return -EOPNOTSUPP; } if (test_dimm_dsm(dimm, ND_INTEL_FW_GET_INFO) == DIMM_DSM_UNSUPPORTED || test_dimm_dsm(dimm, ND_INTEL_FW_START_UPDATE) == DIMM_DSM_UNSUPPORTED || test_dimm_dsm(dimm, ND_INTEL_FW_SEND_DATA) == DIMM_DSM_UNSUPPORTED || test_dimm_dsm(dimm, ND_INTEL_FW_FINISH_UPDATE) == DIMM_DSM_UNSUPPORTED || test_dimm_dsm(dimm, ND_INTEL_FW_FINISH_STATUS_QUERY) == DIMM_DSM_UNSUPPORTED) { dbg(ctx, "unsupported function: %d\n", ND_INTEL_FW_GET_INFO); return -EIO; } return 0; } struct ndctl_dimm_ops * const intel_dimm_ops = &(struct ndctl_dimm_ops) { .cmd_desc = intel_cmd_desc, .new_smart = intel_dimm_cmd_new_smart, .smart_get_flags = intel_cmd_smart_get_flags, .smart_get_health = intel_cmd_smart_get_health, .smart_get_media_temperature = intel_cmd_smart_get_media_temperature, .smart_get_ctrl_temperature = intel_cmd_smart_get_ctrl_temperature, .smart_get_spares = intel_cmd_smart_get_spares, .smart_get_alarm_flags = intel_cmd_smart_get_alarm_flags, .smart_get_life_used = intel_cmd_smart_get_life_used, .smart_get_shutdown_state = intel_cmd_smart_get_shutdown_state, .smart_get_shutdown_count = intel_cmd_smart_get_shutdown_count, .smart_get_vendor_size = intel_cmd_smart_get_vendor_size, .smart_get_vendor_data = intel_cmd_smart_get_vendor_data, .new_smart_threshold = intel_dimm_cmd_new_smart_threshold, .smart_threshold_get_alarm_control = intel_cmd_smart_threshold_get_alarm_control, .smart_threshold_get_media_temperature = intel_cmd_smart_threshold_get_media_temperature, .smart_threshold_get_ctrl_temperature = intel_cmd_smart_threshold_get_ctrl_temperature, .smart_threshold_get_spares = intel_cmd_smart_threshold_get_spares, .new_smart_set_threshold = intel_dimm_cmd_new_smart_set_threshold, .smart_threshold_get_supported_alarms = intel_cmd_smart_threshold_get_supported_alarms, .smart_threshold_set_alarm_control = intel_cmd_smart_threshold_set_alarm_control, .smart_threshold_set_media_temperature = intel_cmd_smart_threshold_set_media_temperature, .smart_threshold_set_ctrl_temperature = intel_cmd_smart_threshold_set_ctrl_temperature, .smart_threshold_set_spares = intel_cmd_smart_threshold_set_spares, .new_smart_inject = intel_new_smart_inject, .smart_inject_media_temperature = intel_cmd_smart_inject_media_temperature, .smart_inject_spares = intel_cmd_smart_inject_spares, .smart_inject_fatal = intel_cmd_smart_inject_fatal, .smart_inject_unsafe_shutdown = intel_cmd_smart_inject_unsafe_shutdown, .smart_inject_supported = intel_dimm_smart_inject_supported, .new_fw_get_info = intel_dimm_cmd_new_fw_get_info, .fw_info_get_storage_size = intel_cmd_fw_info_get_storage_size, .fw_info_get_max_send_len = intel_cmd_fw_info_get_max_send_len, .fw_info_get_query_interval = intel_cmd_fw_info_get_query_interval, .fw_info_get_max_query_time = intel_cmd_fw_info_get_max_query_time, .fw_info_get_run_version = intel_cmd_fw_info_get_run_version, .fw_info_get_updated_version = intel_cmd_fw_info_get_updated_version, .new_fw_start_update = intel_dimm_cmd_new_fw_start, .fw_start_get_context = intel_cmd_fw_start_get_context, .new_fw_send = intel_dimm_cmd_new_fw_send, .new_fw_finish = intel_dimm_cmd_new_fw_finish, .new_fw_abort = intel_dimm_cmd_new_fw_abort, .new_fw_finish_query = intel_dimm_cmd_new_fw_finish_query, .fw_fquery_get_fw_rev = intel_cmd_fw_fquery_get_fw_rev, .fw_xlat_firmware_status = intel_cmd_fw_xlat_firmware_status, .new_ack_shutdown_count = intel_dimm_cmd_new_lss, .fw_update_supported = intel_dimm_fw_update_supported, .xlat_firmware_status = intel_cmd_xlat_firmware_status, }; ndctl-81/ndctl/lib/intel.h000066400000000000000000000117331476737544500156010ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1 */ /* Copyright (C) 2017-2020, Intel Corporation. All rights reserved. */ #ifndef __INTEL_H__ #define __INTEL_H__ #define ND_INTEL_SMART 1 #define ND_INTEL_SMART_THRESHOLD 2 #define ND_INTEL_ENABLE_LSS_STATUS 10 #define ND_INTEL_FW_GET_INFO 12 #define ND_INTEL_FW_START_UPDATE 13 #define ND_INTEL_FW_SEND_DATA 14 #define ND_INTEL_FW_FINISH_UPDATE 15 #define ND_INTEL_FW_FINISH_STATUS_QUERY 16 #define ND_INTEL_SMART_SET_THRESHOLD 17 #define ND_INTEL_SMART_INJECT 18 #define ND_INTEL_SMART_HEALTH_VALID (1 << 0) #define ND_INTEL_SMART_SPARES_VALID (1 << 1) #define ND_INTEL_SMART_USED_VALID (1 << 2) #define ND_INTEL_SMART_MTEMP_VALID (1 << 3) #define ND_INTEL_SMART_CTEMP_VALID (1 << 4) #define ND_INTEL_SMART_SHUTDOWN_COUNT_VALID (1 << 5) #define ND_INTEL_SMART_AIT_STATUS_VALID (1 << 6) #define ND_INTEL_SMART_PTEMP_VALID (1 << 7) #define ND_INTEL_SMART_ALARM_VALID (1 << 9) #define ND_INTEL_SMART_SHUTDOWN_VALID (1 << 10) #define ND_INTEL_SMART_VENDOR_VALID (1 << 11) #define ND_INTEL_SMART_SPARE_TRIP (1 << 0) #define ND_INTEL_SMART_TEMP_TRIP (1 << 1) #define ND_INTEL_SMART_CTEMP_TRIP (1 << 2) #define ND_INTEL_SMART_NON_CRITICAL_HEALTH (1 << 0) #define ND_INTEL_SMART_CRITICAL_HEALTH (1 << 1) #define ND_INTEL_SMART_FATAL_HEALTH (1 << 2) #define ND_INTEL_SMART_INJECT_MTEMP (1 << 0) #define ND_INTEL_SMART_INJECT_SPARE (1 << 1) #define ND_INTEL_SMART_INJECT_FATAL (1 << 2) #define ND_INTEL_SMART_INJECT_SHUTDOWN (1 << 3) struct nd_intel_smart { __u32 status; union { struct { __u32 flags; __u8 reserved0[4]; __u8 health; __u8 spares; __u8 life_used; __u8 alarm_flags; __u16 media_temperature; __u16 ctrl_temperature; __u32 shutdown_count; __u8 ait_status; __u16 pmic_temperature; __u8 reserved1[8]; __u8 shutdown_state; __u32 vendor_size; __u8 vendor_data[92]; } __attribute__((packed)); __u8 data[128]; }; } __attribute__((packed)); struct nd_intel_smart_threshold { __u32 status; union { struct { __u16 alarm_control; __u8 spares; __u16 media_temperature; __u16 ctrl_temperature; __u8 reserved[1]; } __attribute__((packed)); __u8 data[8]; }; } __attribute__((packed)); struct nd_intel_smart_set_threshold { __u16 alarm_control; __u8 spares; __u16 media_temperature; __u16 ctrl_temperature; __u32 status; } __attribute__((packed)); struct nd_intel_smart_inject { __u64 flags; __u8 mtemp_enable; __u16 media_temperature; __u8 spare_enable; __u8 spares; __u8 fatal_enable; __u8 unsafe_shutdown_enable; __u32 status; } __attribute__((packed)); struct nd_intel_fw_info { __u32 status; __u32 storage_size; __u32 max_send_len; __u32 query_interval; __u32 max_query_time; __u8 update_cap; __u8 reserved[3]; __u32 fis_version; __u64 run_version; __u64 updated_version; } __attribute__((packed)); struct nd_intel_fw_start { __u32 status; __u32 context; } __attribute__((packed)); /* this one has the output first because the variable input data size */ struct nd_intel_fw_send_data { __u32 context; __u32 offset; __u32 length; __u8 data[0]; /* reserving last 4 bytes as status */ /* __u32 status; */ } __attribute__((packed)); struct nd_intel_fw_finish_update { __u8 ctrl_flags; __u8 reserved[3]; __u32 context; __u32 status; } __attribute__((packed)); struct nd_intel_fw_finish_query { __u32 context; __u32 status; __u64 updated_fw_rev; } __attribute__((packed)); struct nd_intel_lss { __u8 enable; __u32 status; } __attribute__((packed)); struct nd_pkg_intel { struct nd_cmd_pkg gen; union { struct nd_intel_smart smart; struct nd_intel_smart_inject inject; struct nd_intel_smart_threshold thresh; struct nd_intel_smart_set_threshold set_thresh; struct nd_intel_fw_info info; struct nd_intel_fw_start start; struct nd_intel_fw_send_data send; struct nd_intel_fw_finish_update finish; struct nd_intel_fw_finish_query fquery; struct nd_intel_lss lss; }; }; #define ND_INTEL_STATUS_MASK 0xffff #define ND_INTEL_STATUS_SUCCESS 0 #define ND_INTEL_STATUS_NOTSUPP 1 #define ND_INTEL_STATUS_NOTEXIST 2 #define ND_INTEL_STATUS_INVALPARM 3 #define ND_INTEL_STATUS_HWERR 4 #define ND_INTEL_STATUS_RETRY 5 #define ND_INTEL_STATUS_UNKNOWN 6 #define ND_INTEL_STATUS_EXTEND 7 #define ND_INTEL_STATUS_NORES 8 #define ND_INTEL_STATUS_NOTREADY 9 #define ND_INTEL_STATUS_EXTEND_MASK 0xffff0000 #define ND_INTEL_STATUS_START_BUSY 0x10000 #define ND_INTEL_STATUS_SEND_CTXINVAL 0x10000 #define ND_INTEL_STATUS_FIN_CTXINVAL 0x10000 #define ND_INTEL_STATUS_FIN_DONE 0x20000 #define ND_INTEL_STATUS_FIN_BAD 0x30000 #define ND_INTEL_STATUS_FIN_ABORTED 0x40000 #define ND_INTEL_STATUS_FQ_CTXINVAL 0x10000 #define ND_INTEL_STATUS_FQ_BUSY 0x20000 #define ND_INTEL_STATUS_FQ_BAD 0x30000 #define ND_INTEL_STATUS_FQ_ORDER 0x40000 #define ND_INTEL_STATUS_INJ_DISABLED 0x10000 #endif /* __INTEL_H__ */ ndctl-81/ndctl/lib/libndctl.c000066400000000000000000004505351476737544500162630ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-2.1 // Copyright (C) 2014-2020, Intel Corporation. All rights reserved. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "private.h" static uuid_t null_uuid; /** * DOC: General note, the structure layouts are privately defined. * Access struct member fields with ndctl__get_. This * library is multithread-aware in that it supports multiple * simultaneous reference-counted contexts, but it is not multithread * safe. Also note that there is no coordination between contexts, * changes made in one context instance may not be reflected in another. */ /** * ndctl_sizeof_namespace_index - min size of a namespace index block plus padding */ NDCTL_EXPORT size_t ndctl_sizeof_namespace_index(void) { return ALIGN(sizeof(struct namespace_index), NSINDEX_ALIGN); } /** * ndctl_min_namespace_size - minimum namespace size that btt suports */ NDCTL_EXPORT size_t ndctl_min_namespace_size(void) { return NSLABEL_NAMESPACE_MIN_SIZE; } /** * ndctl_sizeof_namespace_label - single entry size in a dimm label set */ NDCTL_EXPORT size_t ndctl_sizeof_namespace_label(void) { /* TODO: v1.2 label support */ return offsetof(struct namespace_label, type_guid); } NDCTL_EXPORT double ndctl_decode_smart_temperature(unsigned int temp) { bool negative = !!(temp & (1 << 15)); double t; temp &= ~(1 << 15); t = temp; t /= 16; if (negative) t *= -1; return t; } NDCTL_EXPORT unsigned int ndctl_encode_smart_temperature(double temp) { bool negative = false; unsigned int t; if (temp < 0) { negative = true; temp *= -1; } t = temp; t *= 16; if (negative) t |= (1 << 15); return t; } struct ndctl_ctx; /** * struct ndctl_mapping - dimm extent relative to a region * @dimm: backing dimm for the mapping * @offset: dimm relative offset * @length: span of the extent * @position: interleave-order of the extent * * This data can be used to identify the dimm ranges contributing to a * region / interleave-set and identify how regions alias each other. */ struct ndctl_mapping { struct ndctl_region *region; struct ndctl_dimm *dimm; unsigned long long offset, length; int position; struct list_node list; }; /** * struct ndctl_region - container for 'pmem' or 'block' capacity * @module: kernel module * @mappings: number of extent ranges contributing to the region * @size: total capacity of the region before resolving aliasing * @type: integer nd-bus device-type * @type_name: 'pmem' or 'block' * @generation: incremented everytime the region is disabled * @nstype: the resulting type of namespace this region produces * @numa_node: numa node attribute * @target_node: target node were this region to be onlined * * A region may alias between pmem and block-window access methods. The * region driver is tasked with parsing the label (if their is one) and * coordinating configuration with peer regions. * * When a region is disabled a client may have pending references to * namespaces and btts. After a disable event the client can * ndctl_region_cleanup() to clean up invalid objects, or it can * specify the cleanup flag to ndctl_region_disable(). */ struct ndctl_region { struct kmod_module *module; struct ndctl_bus *bus; int id, num_mappings, nstype, range_index, ro; unsigned long align; int mappings_init; int namespaces_init; int btts_init; int pfns_init; int daxs_init; int refresh_type; unsigned long long size; char *region_path; char *region_buf; int buf_len; int generation; int numa_node, target_node; struct list_head btts; struct list_head pfns; struct list_head daxs; struct list_head mappings; struct list_head namespaces; struct list_head stale_namespaces; struct list_head stale_btts; struct list_head stale_pfns; struct list_head stale_daxs; struct list_node list; /** * struct ndctl_interleave_set - extra info for interleave sets * @state: are any interleave set members active or all idle * @cookie: summary cookie identifying the NFIT config for the set */ struct ndctl_interleave_set { int state; unsigned long long cookie; } iset; struct badblocks_iter bb_iter; enum ndctl_persistence_domain persistence_domain; /* file descriptor for deep flush sysfs entry */ int flush_fd; }; /** * struct ndctl_btt - stacked block device provided sector atomicity * @module: kernel module (nd_btt) * @lbasize: sector size info * @size: usable size of the btt after removing metadata etc * @ndns: host namespace for the btt instance * @region: parent region * @btt_path: btt devpath * @uuid: unique identifier for a btt instance * @btt_buf: space to print paths for bind/unbind operations * @bdev: block device associated with a btt */ struct ndctl_btt { struct kmod_module *module; struct ndctl_region *region; struct ndctl_namespace *ndns; struct list_node list; struct ndctl_lbasize lbasize; unsigned long long size; char *btt_path; char *btt_buf; char *bdev; int buf_len; uuid_t uuid; int id, generation; }; /** * struct ndctl_pfn - reservation for per-page-frame metadata * @module: kernel module (nd_pfn) * @ndns: host namespace for the pfn instance * @loc: host metadata location (ram or pmem (default)) * @align: data offset alignment * @region: parent region * @pfn_path: pfn devpath * @uuid: unique identifier for a pfn instance * @pfn_buf: space to print paths for bind/unbind operations * @bdev: block device associated with a pfn */ struct ndctl_pfn { struct kmod_module *module; struct ndctl_region *region; struct ndctl_namespace *ndns; struct list_node list; enum ndctl_pfn_loc loc; unsigned long align; unsigned long long resource, size; char *pfn_path; char *pfn_buf; char *bdev; int buf_len; uuid_t uuid; int id, generation; struct ndctl_lbasize alignments; }; struct ndctl_dax { struct ndctl_pfn pfn; struct daxctl_region *region; }; /** * ndctl_get_userdata - retrieve stored data pointer from library context * @ctx: ndctl library context * * This might be useful to access from callbacks like a custom logging * function. */ NDCTL_EXPORT void *ndctl_get_userdata(struct ndctl_ctx *ctx) { if (ctx == NULL) return NULL; return ctx->userdata; } /** * ndctl_set_userdata - store custom @userdata in the library context * @ctx: ndctl library context * @userdata: data pointer */ NDCTL_EXPORT void ndctl_set_userdata(struct ndctl_ctx *ctx, void *userdata) { if (ctx == NULL) return; ctx->userdata = userdata; } NDCTL_EXPORT int ndctl_set_config_path(struct ndctl_ctx *ctx, char *config_path) { if ((!ctx) || (!config_path)) return -EINVAL; ctx->config_path = config_path; return 0; } NDCTL_EXPORT const char *ndctl_get_config_path(struct ndctl_ctx *ctx) { if (ctx == NULL) return NULL; return ctx->config_path; } /** * ndctl_new - instantiate a new library context * @ctx: context to establish * * Returns zero on success and stores an opaque pointer in ctx. The * context is freed by ndctl_unref(), i.e. ndctl_new() implies an * internal ndctl_ref(). */ NDCTL_EXPORT int ndctl_new(struct ndctl_ctx **ctx) { struct daxctl_ctx *daxctl_ctx; struct kmod_ctx *kmod_ctx; struct ndctl_ctx *c; struct udev *udev; const char *env; int rc = 0; udev = udev_new(); if (check_udev(udev) != 0) return -ENXIO; kmod_ctx = kmod_new(NULL, NULL); if (check_kmod(kmod_ctx) != 0) { rc = -ENXIO; goto err_kmod; } rc = daxctl_new(&daxctl_ctx); if (rc) goto err_daxctl; c = calloc(1, sizeof(struct ndctl_ctx)); if (!c) { rc = -ENOMEM; goto err_ctx; } c->refcount = 1; log_init(&c->ctx, "libndctl", "NDCTL_LOG"); c->udev = udev; c->timeout = 5000; list_head_init(&c->busses); info(c, "ctx %p created\n", c); dbg(c, "log_priority=%d\n", c->ctx.log_priority); *ctx = c; env = secure_getenv("NDCTL_TIMEOUT"); if (env != NULL) { unsigned long tmo; char *end; tmo = strtoul(env, &end, 0); if (tmo < ULONG_MAX && !*end) c->timeout = tmo; dbg(c, "timeout = %ld\n", tmo); } c->udev_queue = udev_queue_new(udev); if (!c->udev_queue) err(c, "failed to retrieve udev queue\n"); rc = ndctl_set_config_path(c, NDCTL_CONF_DIR); if (rc) dbg(c, "Unable to set config path: %s\n", strerror(-rc)); c->kmod_ctx = kmod_ctx; c->daxctl_ctx = daxctl_ctx; return 0; err_ctx: daxctl_unref(daxctl_ctx); err_daxctl: kmod_unref(kmod_ctx); err_kmod: udev_unref(udev); return rc; } NDCTL_EXPORT void ndctl_set_private_data(struct ndctl_ctx *ctx, void *data) { ctx->private_data = data; } NDCTL_EXPORT void *ndctl_get_private_data(struct ndctl_ctx *ctx) { return ctx->private_data; } NDCTL_EXPORT struct daxctl_ctx *ndctl_get_daxctl_ctx(struct ndctl_ctx *ctx) { return ctx->daxctl_ctx; } /** * ndctl_ref - take an additional reference on the context * @ctx: context established by ndctl_new() */ NDCTL_EXPORT struct ndctl_ctx *ndctl_ref(struct ndctl_ctx *ctx) { if (ctx == NULL) return NULL; ctx->refcount++; return ctx; } static void badblocks_iter_free(struct badblocks_iter *bb_iter) { if (bb_iter->file) fclose(bb_iter->file); } static int badblocks_iter_init(struct badblocks_iter *bb_iter, const char *path) { char *bb_path; int rc = 0; /* if the file is already open */ if (bb_iter->file) { fclose(bb_iter->file); bb_iter->file = NULL; } if (asprintf(&bb_path, "%s/badblocks", path) < 0) return -errno; bb_iter->file = fopen(bb_path, "re"); if (!bb_iter->file) { rc = -errno; free(bb_path); return rc; } free(bb_path); return rc; } static struct badblock *badblocks_iter_next(struct badblocks_iter *bb_iter) { int rc; char *buf = NULL; size_t rlen = 0; if (!bb_iter->file) return NULL; rc = getline(&buf, &rlen, bb_iter->file); if (rc == -1) { free(buf); return NULL; } rc = sscanf(buf, "%llu %u", &bb_iter->bb.offset, &bb_iter->bb.len); free(buf); if (rc != 2) { fclose(bb_iter->file); bb_iter->file = NULL; bb_iter->bb.offset = 0; bb_iter->bb.len = 0; return NULL; } return &bb_iter->bb; } static struct badblock *badblocks_iter_first(struct badblocks_iter *bb_iter, struct ndctl_ctx *ctx, const char *path) { int rc; rc = badblocks_iter_init(bb_iter, path); if (rc < 0) return NULL; return badblocks_iter_next(bb_iter); } static void free_namespace(struct ndctl_namespace *ndns, struct list_head *head) { struct ndctl_bb *bb, *next; if (head) list_del_from(head, &ndns->list); list_for_each_safe(&ndns->injected_bb, bb, next, list) free(bb); free(ndns->lbasize.supported); free(ndns->ndns_path); free(ndns->ndns_buf); free(ndns->bdev); free(ndns->alt_name); badblocks_iter_free(&ndns->bb_iter); kmod_module_unref(ndns->module); free(ndns); } static void free_namespaces(struct ndctl_region *region) { struct ndctl_namespace *ndns, *_n; list_for_each_safe(®ion->namespaces, ndns, _n, list) free_namespace(ndns, ®ion->namespaces); } static void free_stale_namespaces(struct ndctl_region *region) { struct ndctl_namespace *ndns, *_n; list_for_each_safe(®ion->stale_namespaces, ndns, _n, list) free_namespace(ndns, ®ion->stale_namespaces); } static void free_btt(struct ndctl_btt *btt, struct list_head *head) { if (head) list_del_from(head, &btt->list); kmod_module_unref(btt->module); free(btt->lbasize.supported); free(btt->btt_path); free(btt->btt_buf); free(btt->bdev); free(btt); } static void free_btts(struct ndctl_region *region) { struct ndctl_btt *btt, *_b; list_for_each_safe(®ion->btts, btt, _b, list) free_btt(btt, ®ion->btts); } static void free_stale_btts(struct ndctl_region *region) { struct ndctl_btt *btt, *_b; list_for_each_safe(®ion->stale_btts, btt, _b, list) free_btt(btt, ®ion->stale_btts); } static void __free_pfn(struct ndctl_pfn *pfn, struct list_head *head, void *to_free) { if (head) list_del_from(head, &pfn->list); kmod_module_unref(pfn->module); free(pfn->pfn_path); free(pfn->pfn_buf); free(pfn->bdev); free(pfn->alignments.supported); free(to_free); } static void free_pfn(struct ndctl_pfn *pfn, struct list_head *head) { __free_pfn(pfn, head, pfn); } static void free_dax(struct ndctl_dax *dax, struct list_head *head) { __free_pfn(&dax->pfn, head, dax); } static void free_pfns(struct ndctl_region *region) { struct ndctl_pfn *pfn, *_b; list_for_each_safe(®ion->pfns, pfn, _b, list) free_pfn(pfn, ®ion->pfns); } static void free_daxs(struct ndctl_region *region) { struct ndctl_dax *dax, *_b; list_for_each_safe(®ion->daxs, dax, _b, pfn.list) free_dax(dax, ®ion->daxs); } static void free_stale_pfns(struct ndctl_region *region) { struct ndctl_pfn *pfn, *_b; list_for_each_safe(®ion->stale_pfns, pfn, _b, list) free_pfn(pfn, ®ion->stale_pfns); } static void free_stale_daxs(struct ndctl_region *region) { struct ndctl_dax *dax, *_b; list_for_each_safe(®ion->stale_daxs, dax, _b, pfn.list) free_dax(dax, ®ion->stale_daxs); } static void free_region(struct ndctl_region *region) { struct ndctl_bus *bus = region->bus; struct ndctl_mapping *mapping, *_m; list_for_each_safe(®ion->mappings, mapping, _m, list) { list_del_from(®ion->mappings, &mapping->list); free(mapping); } free_btts(region); free_stale_btts(region); free_pfns(region); free_stale_pfns(region); free_daxs(region); free_stale_daxs(region); free_namespaces(region); free_stale_namespaces(region); list_del_from(&bus->regions, ®ion->list); kmod_module_unref(region->module); free(region->region_buf); free(region->region_path); badblocks_iter_free(®ion->bb_iter); if (region->flush_fd > 0) close(region->flush_fd); free(region); } static void free_dimm(struct ndctl_dimm *dimm) { if (!dimm) return; free(dimm->unique_id); free(dimm->dimm_buf); free(dimm->dimm_path); free(dimm->bus_prefix); if (dimm->module) kmod_module_unref(dimm->module); if (dimm->health_eventfd > -1) close(dimm->health_eventfd); ndctl_cmd_unref(dimm->ndd.cmd_read); free(dimm); } static void free_bus(struct ndctl_bus *bus, struct list_head *head) { struct ndctl_dimm *dimm, *_d; struct ndctl_region *region, *_r; list_for_each_safe(&bus->dimms, dimm, _d, list) { list_del_from(&bus->dimms, &dimm->list); free_dimm(dimm); } list_for_each_safe(&bus->regions, region, _r, list) free_region(region); if (head) list_del_from(head, &bus->list); free(bus->provider); free(bus->bus_path); free(bus->bus_buf); free(bus->wait_probe_path); free(bus->scrub_path); free(bus); } static void free_context(struct ndctl_ctx *ctx) { struct ndctl_bus *bus, *_b; list_for_each_safe(&ctx->busses, bus, _b, list) free_bus(bus, &ctx->busses); free(ctx); } /** * ndctl_unref - drop a context reference count * @ctx: context established by ndctl_new() * * Drop a reference and if the resulting reference count is 0 destroy * the context. */ NDCTL_EXPORT struct ndctl_ctx *ndctl_unref(struct ndctl_ctx *ctx) { if (ctx == NULL) return NULL; ctx->refcount--; if (ctx->refcount > 0) return NULL; udev_queue_unref(ctx->udev_queue); udev_unref(ctx->udev); kmod_unref(ctx->kmod_ctx); daxctl_unref(ctx->daxctl_ctx); info(ctx, "context %p released\n", ctx); free_context(ctx); return NULL; } /** * ndctl_set_log_fn - override default log routine * @ctx: ndctl library context * @log_fn: function to be called for logging messages * * The built-in logging writes to stderr. It can be overridden by a * custom function, to plug log messages into the user's logging * functionality. */ NDCTL_EXPORT void ndctl_set_log_fn(struct ndctl_ctx *ctx, void (*ndctl_log_fn)(struct ndctl_ctx *ctx, int priority, const char *file, int line, const char *fn, const char *format, va_list args)) { ctx->ctx.log_fn = (log_fn) ndctl_log_fn; info(ctx, "custom logging function %p registered\n", ndctl_log_fn); } /** * ndctl_get_log_priority - retrieve current library loglevel (syslog) * @ctx: ndctl library context */ NDCTL_EXPORT int ndctl_get_log_priority(struct ndctl_ctx *ctx) { return ctx->ctx.log_priority; } /** * ndctl_set_log_priority - set log verbosity * @priority: from syslog.h, LOG_ERR, LOG_INFO, LOG_DEBUG * * Note: LOG_DEBUG requires library be built with "configure --enable-debug" */ NDCTL_EXPORT void ndctl_set_log_priority(struct ndctl_ctx *ctx, int priority) { ctx->ctx.log_priority = priority; /* forward the debug level to our internal libdaxctl instance */ daxctl_set_log_priority(ctx->daxctl_ctx, priority); } static char *__dev_path(char *type, unsigned int major, unsigned int minor, int parent) { char *path, *dev_path; if (asprintf(&path, "/sys/dev/%s/%u:%u%s", type, major, minor, parent ? "/device" : "") < 0) return NULL; dev_path = realpath(path, NULL); free(path); return dev_path; } static char *parent_dev_path(char *type, unsigned int major, unsigned int minor) { return __dev_path(type, major, minor, 1); } static int device_parse(struct ndctl_ctx *ctx, struct ndctl_bus *bus, const char *base_path, const char *dev_name, void *parent, add_dev_fn add_dev) { if (bus) ndctl_bus_wait_probe(bus); return sysfs_device_parse(ctx, base_path, dev_name, parent, add_dev); } static int to_cmd_index(const char *name, int dimm) { const char *(*cmd_name_fn)(unsigned cmd); int i, end_cmd; if (dimm) { end_cmd = ND_CMD_CALL; cmd_name_fn = nvdimm_cmd_name; } else { end_cmd = ND_CMD_CLEAR_ERROR; cmd_name_fn = nvdimm_bus_cmd_name; } for (i = 1; i <= end_cmd; i++) { const char *cmd_name = cmd_name_fn(i); if (!cmd_name) continue; if (strcmp(name, cmd_name) == 0) return i; } return 0; } static unsigned long parse_commands(char *commands, int dimm) { unsigned long cmd_mask = 0; char *start, *end; start = commands; while ((end = strchr(start, ' '))) { int cmd; *end = '\0'; cmd = to_cmd_index(start, dimm); if (cmd) cmd_mask |= 1 << cmd; start = end + 1; } return cmd_mask; } static void parse_nfit_mem_flags(struct ndctl_dimm *dimm, char *flags) { char *start, *end; start = flags; while ((end = strchr(start, ' '))) { *end = '\0'; if (strcmp(start, "not_armed") == 0) dimm->flags.f_arm = 1; else if (strcmp(start, "save_fail") == 0) dimm->flags.f_save = 1; else if (strcmp(start, "flush_fail") == 0) dimm->flags.f_flush = 1; else if (strcmp(start, "smart_event") == 0) dimm->flags.f_smart = 1; else if (strcmp(start, "restore_fail") == 0) dimm->flags.f_restore = 1; else if (strcmp(start, "map_fail") == 0) dimm->flags.f_map = 1; else if (strcmp(start, "smart_notify") == 0) dimm->flags.f_notify = 1; start = end + 1; } if (end != start) dbg(ndctl_dimm_get_ctx(dimm), "%s: %s\n", ndctl_dimm_get_devname(dimm), flags); } static void parse_papr_flags(struct ndctl_dimm *dimm, char *flags) { struct ndctl_ctx *ctx = ndctl_dimm_get_ctx(dimm); char *start, *end; start = flags; while ((end = strchr(start, ' '))) { *end = '\0'; if (strcmp(start, "not_armed") == 0) dimm->flags.f_arm = 1; else if (strcmp(start, "flush_fail") == 0) dimm->flags.f_flush = 1; else if (strcmp(start, "restore_fail") == 0) dimm->flags.f_restore = 1; else if (strcmp(start, "smart_notify") == 0) dimm->flags.f_smart = 1; else if (strcmp(start, "save_fail") == 0) dimm->flags.f_save = 1; start = end + 1; } if (end != start) dbg(ctx, "%s: Flags:%s\n", ndctl_dimm_get_devname(dimm), flags); } static void parse_dimm_flags(struct ndctl_dimm *dimm, char *flags) { char *start, *end; dimm->locked = 0; dimm->aliased = 0; start = flags; while ((end = strchr(start, ' '))) { *end = '\0'; if (strcmp(start, "lock") == 0) dimm->locked = 1; else if (strcmp(start, "alias") == 0) dimm->aliased = 1; start = end + 1; } if (end != start) dbg(ndctl_dimm_get_ctx(dimm), "%s: %s\n", ndctl_dimm_get_devname(dimm), flags); } static enum ndctl_fwa_state fwa_to_state(const char *fwa) { if (strcmp(fwa, "idle") == 0) return NDCTL_FWA_IDLE; if (strcmp(fwa, "busy") == 0) return NDCTL_FWA_BUSY; if (strcmp(fwa, "armed") == 0) return NDCTL_FWA_ARMED; if (strcmp(fwa, "overflow") == 0) return NDCTL_FWA_ARM_OVERFLOW; return NDCTL_FWA_INVALID; } static enum ndctl_fwa_method fwa_method_to_method(const char *fwa_method) { if (!fwa_method) return NDCTL_FWA_METHOD_RESET; if (strcmp(fwa_method, "quiesce") == 0) return NDCTL_FWA_METHOD_SUSPEND; if (strcmp(fwa_method, "live") == 0) return NDCTL_FWA_METHOD_LIVE; return NDCTL_FWA_METHOD_RESET; } static int is_subsys_cxl(const char *subsys) { char *path; int rc; path = realpath(subsys, NULL); if (!path) return -errno; if (!strcmp(subsys, "/sys/bus/cxl")) rc = 1; else rc = 0; free(path); return rc; } static void *add_bus(void *parent, int id, const char *ctl_base) { char buf[SYSFS_ATTR_SIZE]; struct ndctl_ctx *ctx = parent; struct ndctl_bus *bus, *bus_dup; char *path = calloc(1, strlen(ctl_base) + 100); if (!path) return NULL; bus = calloc(1, sizeof(*bus)); if (!bus) goto err_bus; list_head_init(&bus->dimms); list_head_init(&bus->regions); bus->ctx = ctx; bus->id = id; sprintf(path, "%s/dev", ctl_base); if (sysfs_read_attr(ctx, path, buf) < 0 || sscanf(buf, "%d:%d", &bus->major, &bus->minor) != 2) goto err_read; sprintf(path, "%s/device/commands", ctl_base); if (sysfs_read_attr(ctx, path, buf) < 0) goto err_read; bus->cmd_mask = parse_commands(buf, 0); sprintf(path, "%s/device/nfit/revision", ctl_base); if (sysfs_read_attr(ctx, path, buf) < 0) { bus->has_nfit = 0; bus->revision = -1; } else { bus->has_nfit = 1; bus->revision = strtoul(buf, NULL, 0); } sprintf(path, "%s/device/of_node/compatible", ctl_base); if (sysfs_read_attr(ctx, path, buf) < 0) bus->has_of_node = 0; else bus->has_of_node = 1; sprintf(path, "%s/device/../subsys", ctl_base); if (is_subsys_cxl(path)) bus->has_cxl = 1; else bus->has_cxl = 0; sprintf(path, "%s/device/nfit/dsm_mask", ctl_base); if (sysfs_read_attr(ctx, path, buf) < 0) bus->nfit_dsm_mask = 0; else bus->nfit_dsm_mask = strtoul(buf, NULL, 0); sprintf(path, "%s/device/provider", ctl_base); if (sysfs_read_attr(ctx, path, buf) < 0) goto err_read; bus->provider = strdup(buf); if (!bus->provider) goto err_read; sprintf(path, "%s/device/wait_probe", ctl_base); bus->wait_probe_path = strdup(path); if (!bus->wait_probe_path) goto err_read; if (ndctl_bus_has_nfit(bus)) { sprintf(path, "%s/device/nfit/scrub", ctl_base); bus->scrub_path = strdup(path); if (!bus->scrub_path) goto err_read; } else { bus->scrub_path = NULL; } sprintf(path, "%s/device/firmware/activate", ctl_base); if (sysfs_read_attr(ctx, path, buf) < 0) bus->fwa_state = NDCTL_FWA_INVALID; else bus->fwa_state = fwa_to_state(buf); sprintf(path, "%s/device/firmware/capability", ctl_base); if (sysfs_read_attr(ctx, path, buf) < 0) bus->fwa_method = fwa_method_to_method(NULL); else bus->fwa_method = fwa_method_to_method(buf); bus->bus_path = parent_dev_path("char", bus->major, bus->minor); if (!bus->bus_path) goto err_dev_path; bus->bus_buf = calloc(1, strlen(bus->bus_path) + 50); if (!bus->bus_buf) goto err_read; bus->buf_len = strlen(bus->bus_path) + 50; ndctl_bus_foreach(ctx, bus_dup) if (strcmp(ndctl_bus_get_provider(bus_dup), ndctl_bus_get_provider(bus)) == 0 && strcmp(ndctl_bus_get_devname(bus_dup), ndctl_bus_get_devname(bus)) == 0) { free_bus(bus, NULL); free(path); return bus_dup; } list_add(&ctx->busses, &bus->list); free(path); return bus; err_dev_path: err_read: free(bus->wait_probe_path); free(bus->scrub_path); free(bus->provider); free(bus->bus_path); free(bus->bus_buf); free(bus); err_bus: free(path); return NULL; } static void busses_init(struct ndctl_ctx *ctx) { if (ctx->busses_init) return; ctx->busses_init = 1; device_parse(ctx, NULL, "/sys/class/nd", "ndctl", ctx, add_bus); } NDCTL_EXPORT void ndctl_invalidate(struct ndctl_ctx *ctx) { ctx->busses_init = 0; } /** * ndctl_bus_get_first - retrieve first "nd bus" in the system * @ctx: context established by ndctl_new * * Returns an ndctl_bus if an nd bus exists in the system. This return * value can be used to iterate to the next available bus in the system * ia ndctl_bus_get_next() */ NDCTL_EXPORT struct ndctl_bus *ndctl_bus_get_first(struct ndctl_ctx *ctx) { busses_init(ctx); return list_top(&ctx->busses, struct ndctl_bus, list); } /** * ndctl_bus_get_next - retrieve the "next" nd bus in the system * @bus: ndctl_bus instance returned from ndctl_bus_get_{first|next} * * Returns NULL if @bus was the "last" bus available in the system */ NDCTL_EXPORT struct ndctl_bus *ndctl_bus_get_next(struct ndctl_bus *bus) { struct ndctl_ctx *ctx = bus->ctx; return list_next(&ctx->busses, bus, list); } NDCTL_EXPORT int ndctl_bus_has_nfit(struct ndctl_bus *bus) { return bus->has_nfit; } NDCTL_EXPORT int ndctl_bus_has_of_node(struct ndctl_bus *bus) { return bus->has_of_node; } NDCTL_EXPORT int ndctl_bus_has_cxl(struct ndctl_bus *bus) { return bus->has_cxl; } NDCTL_EXPORT int ndctl_bus_is_papr_scm(struct ndctl_bus *bus) { char buf[SYSFS_ATTR_SIZE]; snprintf(bus->bus_buf, bus->buf_len, "%s/of_node/compatible", bus->bus_path); if (sysfs_read_attr(bus->ctx, bus->bus_buf, buf) < 0) return 0; return (strcmp(buf, "ibm,pmemory") == 0 || strcmp(buf, "nvdimm_test") == 0); } /** * ndctl_bus_get_major - nd bus character device major number * @bus: ndctl_bus instance returned from ndctl_bus_get_{first|next} */ NDCTL_EXPORT unsigned int ndctl_bus_get_major(struct ndctl_bus *bus) { return bus->major; } /** * ndctl_bus_get_minor - nd bus character device minor number * @bus: ndctl_bus instance returned from ndctl_bus_get_{first|next} */ NDCTL_EXPORT unsigned int ndctl_bus_get_minor(struct ndctl_bus *bus) { return bus->minor; } NDCTL_EXPORT const char *ndctl_bus_get_devname(struct ndctl_bus *bus) { return devpath_to_devname(bus->bus_path); } NDCTL_EXPORT struct ndctl_bus *ndctl_bus_get_by_provider(struct ndctl_ctx *ctx, const char *provider) { struct ndctl_bus *bus; ndctl_bus_foreach(ctx, bus) if (strcmp(provider, ndctl_bus_get_provider(bus)) == 0) return bus; return NULL; } NDCTL_EXPORT enum ndctl_persistence_domain ndctl_bus_get_persistence_domain(struct ndctl_bus *bus) { struct ndctl_region *region; enum ndctl_persistence_domain pd = -1; /* iterate through region to get the region persistence domain */ ndctl_region_foreach(bus, region) { /* we are looking for the least persistence domain */ if (pd < region->persistence_domain) pd = region->persistence_domain; } return pd < 0 ? PERSISTENCE_UNKNOWN : pd; } NDCTL_EXPORT struct ndctl_btt *ndctl_region_get_btt_seed(struct ndctl_region *region) { struct ndctl_ctx *ctx = ndctl_region_get_ctx(region); char *path = region->region_buf; int len = region->buf_len; struct ndctl_btt *btt; char buf[SYSFS_ATTR_SIZE]; if (snprintf(path, len, "%s/btt_seed", region->region_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_region_get_devname(region)); return NULL; } if (sysfs_read_attr(ctx, path, buf) < 0) return NULL; ndctl_btt_foreach(region, btt) if (strcmp(buf, ndctl_btt_get_devname(btt)) == 0) return btt; return NULL; } NDCTL_EXPORT struct ndctl_pfn *ndctl_region_get_pfn_seed(struct ndctl_region *region) { struct ndctl_ctx *ctx = ndctl_region_get_ctx(region); char *path = region->region_buf; int len = region->buf_len; struct ndctl_pfn *pfn; char buf[SYSFS_ATTR_SIZE]; if (snprintf(path, len, "%s/pfn_seed", region->region_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_region_get_devname(region)); return NULL; } if (sysfs_read_attr(ctx, path, buf) < 0) return NULL; ndctl_pfn_foreach(region, pfn) if (strcmp(buf, ndctl_pfn_get_devname(pfn)) == 0) return pfn; return NULL; } NDCTL_EXPORT struct ndctl_dax *ndctl_region_get_dax_seed(struct ndctl_region *region) { struct ndctl_ctx *ctx = ndctl_region_get_ctx(region); char *path = region->region_buf; int len = region->buf_len; struct ndctl_dax *dax; char buf[SYSFS_ATTR_SIZE]; if (snprintf(path, len, "%s/dax_seed", region->region_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_region_get_devname(region)); return NULL; } if (sysfs_read_attr(ctx, path, buf) < 0) return NULL; ndctl_dax_foreach(region, dax) if (strcmp(buf, ndctl_dax_get_devname(dax)) == 0) return dax; return NULL; } NDCTL_EXPORT int ndctl_region_get_ro(struct ndctl_region *region) { return region->ro; } NDCTL_EXPORT int ndctl_region_set_ro(struct ndctl_region *region, int ro) { struct ndctl_ctx *ctx = ndctl_region_get_ctx(region); char *path = region->region_buf; int len = region->buf_len, rc; if (snprintf(path, len, "%s/read_only", region->region_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_region_get_devname(region)); return -ENXIO; } ro = !!ro; rc = sysfs_write_attr(ctx, path, ro ? "1\n" : "0\n"); if (rc < 0) return rc; region->ro = ro; return ro; } NDCTL_EXPORT unsigned long ndctl_region_get_align(struct ndctl_region *region) { return region->align; } /** * ndctl_region_set_align() - Align namespace dpa allocations to @align * @region: region to modify * @align: alignment that must be a power-of-2 and >= the platform minimum * * WARNING: setting the region align value to anything less than the * kernel default (16M) may result in namespaces that are not cross-arch * (PowerPC) compatible. The minimum alignment for raw mode namespaces * is PAGE_SIZE. */ NDCTL_EXPORT int ndctl_region_set_align(struct ndctl_region *region, unsigned long align) { struct ndctl_ctx *ctx = ndctl_region_get_ctx(region); char *path = region->region_buf; int len = region->buf_len, rc; char buf[SYSFS_ATTR_SIZE]; if (snprintf(path, len, "%s/align", region->region_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_region_get_devname(region)); return -ENXIO; } sprintf(buf, "%#lx\n", align); rc = sysfs_write_attr(ctx, path, buf); if (rc < 0) return rc; region->align = align; return 0; } NDCTL_EXPORT unsigned long long ndctl_region_get_resource(struct ndctl_region *region) { struct ndctl_ctx *ctx = ndctl_region_get_ctx(region); char *path = region->region_buf; int len = region->buf_len; char buf[SYSFS_ATTR_SIZE]; int rc; if (snprintf(path, len, "%s/resource", region->region_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_region_get_devname(region)); errno = ENOMEM; return ULLONG_MAX; } rc = sysfs_read_attr(ctx, path, buf); if (rc < 0) { errno = -rc; return ULLONG_MAX; } return strtoull(buf, NULL, 0); } NDCTL_EXPORT int ndctl_region_deep_flush(struct ndctl_region *region) { int rc = pwrite(region->flush_fd, "1\n", 1, 0); return (rc == -1) ? -errno : 0; } NDCTL_EXPORT const char *ndctl_bus_get_cmd_name(struct ndctl_bus *bus, int cmd) { return nvdimm_bus_cmd_name(cmd); } NDCTL_EXPORT int ndctl_bus_is_cmd_supported(struct ndctl_bus *bus, int cmd) { return !!(bus->cmd_mask & (1ULL << cmd)); } NDCTL_EXPORT unsigned int ndctl_bus_get_revision(struct ndctl_bus *bus) { return bus->revision; } NDCTL_EXPORT unsigned int ndctl_bus_get_id(struct ndctl_bus *bus) { return bus->id; } NDCTL_EXPORT const char *ndctl_bus_get_provider(struct ndctl_bus *bus) { return bus->provider; } NDCTL_EXPORT struct ndctl_ctx *ndctl_bus_get_ctx(struct ndctl_bus *bus) { return bus->ctx; } /** * ndctl_bus_wait_probe - flush bus async probing * @bus: bus to sync * * Upon return this bus's dimm and region devices are probed, the region * child namespace devices are registered, and drivers for namespaces * and btts are loaded (if module policy allows) */ NDCTL_EXPORT int ndctl_bus_wait_probe(struct ndctl_bus *bus) { struct ndctl_ctx *ctx = ndctl_bus_get_ctx(bus); unsigned long tmo = ctx->timeout; char buf[SYSFS_ATTR_SIZE]; int rc, sleep = 0; do { rc = sysfs_read_attr(bus->ctx, bus->wait_probe_path, buf); if (rc < 0) break; if (!ctx->udev_queue) break; if (udev_queue_get_queue_is_empty(ctx->udev_queue)) break; sleep++; usleep(1000); } while (ctx->timeout == 0 || tmo-- != 0); if (sleep) dbg(ctx, "waited %d millisecond%s for bus%d...\n", sleep, sleep == 1 ? "" : "s", ndctl_bus_get_id(bus)); return rc < 0 ? -ENXIO : 0; } static int __ndctl_bus_get_scrub_state(struct ndctl_bus *bus, unsigned int *scrub_count, bool *active) { struct ndctl_ctx *ctx = ndctl_bus_get_ctx(bus); char buf[SYSFS_ATTR_SIZE]; char in_progress = '\0'; int rc; rc = sysfs_read_attr(ctx, bus->scrub_path, buf); if (rc < 0) return -EOPNOTSUPP; rc = sscanf(buf, "%u%c", scrub_count, &in_progress); if (rc < 0) return -ENXIO; switch (rc) { case 1: *active = false; return 0; case 2: if (in_progress == '+') { *active = true; return 0; } /* fall through */ default: /* unable to read scrub count */ return -ENXIO; } } NDCTL_EXPORT int ndctl_bus_start_scrub(struct ndctl_bus *bus) { struct ndctl_ctx *ctx = ndctl_bus_get_ctx(bus); int rc; if (bus->scrub_path == NULL) return -EOPNOTSUPP; rc = sysfs_write_attr(ctx, bus->scrub_path, "1\n"); /* * Try at least 1 poll cycle before reporting busy in case this * request hits the kernel's exponential backoff while the * hardware/platform scrub state is idle. */ if (rc == -EBUSY && ndctl_bus_poll_scrub_completion(bus, 1, 1) == 0) return sysfs_write_attr(ctx, bus->scrub_path, "1\n"); return rc; } NDCTL_EXPORT int ndctl_bus_get_scrub_state(struct ndctl_bus *bus) { unsigned int scrub_count = 0; bool active = false; int rc; rc = __ndctl_bus_get_scrub_state(bus, &scrub_count, &active); if (rc < 0) return rc; return active; } NDCTL_EXPORT unsigned int ndctl_bus_get_scrub_count(struct ndctl_bus *bus) { unsigned int scrub_count = 0; bool active = false; int rc; rc = __ndctl_bus_get_scrub_state(bus, &scrub_count, &active); if (rc) { errno = -rc; return UINT_MAX; } return scrub_count; } /** * ndctl_bus_poll_scrub_completion - wait for a scrub to complete * @bus: bus for which to check whether a scrub is in progress * @poll_interval: nr seconds between wake up and re-read the status * @timeout: total number of seconds to wait * * Upon return this bus has completed any in-progress scrubs if @timeout * is 0 otherwise -ETIMEDOUT when @timeout seconds have expired. This * is different from ndctl_cmd_ars_in_progress in that the latter checks * the output of an ars_status command to see if the in-progress flag is * set, i.e. provides the firmware's view of whether a scrub is in * progress. ndctl_bus_wait_for_scrub_completion() instead checks the * kernel's view of whether a scrub is in progress by looking at the * 'scrub' file in sysfs. * * The @poll_interval option changes the frequency at which the kernel * status is polled, but it requires a supporting kernel for that poll * interval to be reflected to the kernel's polling of the ARS * interface. Kernel's with poll interval support limit that polling to * root (CAP_SYS_RAWIO) processes. */ NDCTL_EXPORT int ndctl_bus_poll_scrub_completion(struct ndctl_bus *bus, unsigned int poll_interval, unsigned int timeout) { struct ndctl_ctx *ctx = ndctl_bus_get_ctx(bus); const char *provider = ndctl_bus_get_provider(bus); char buf[SYSFS_ATTR_SIZE] = { 0 }; unsigned int scrub_count; struct pollfd fds; char in_progress; int fd = 0, rc; if (bus->scrub_path == NULL) return -EOPNOTSUPP; fd = open(bus->scrub_path, O_RDONLY|O_CLOEXEC); if (fd < 0) return -errno; memset(&fds, 0, sizeof(fds)); fds.fd = fd; for (;;) { rc = sysfs_read_attr(ctx, bus->scrub_path, buf); if (rc < 0) { rc = -EOPNOTSUPP; break; } rc = sscanf(buf, "%u%c", &scrub_count, &in_progress); if (rc < 0) { rc = -EOPNOTSUPP; break; } if (rc == 1) { /* scrub complete, break successfully */ rc = 0; break; } else if (rc == 2 && in_progress == '+') { long tmo; if (!timeout) tmo = poll_interval; else if (!poll_interval) tmo = timeout; else tmo = min(poll_interval, timeout); tmo *= 1000; if (tmo == 0) tmo = -1; /* scrub in progress, wait */ rc = poll(&fds, 1, tmo); dbg(ctx, "%s: poll wake: rc: %d status: \'%s\'\n", provider, rc, buf); if (rc > 0) fds.revents = 0; if (pread(fd, buf, 1, 0) == -1) { rc = -errno; break; } if (rc < 0) { rc = -errno; dbg(ctx, "%s: poll error: %s\n", provider, strerror(errno)); break; } else if (rc == 0) { dbg(ctx, "%s: poll timeout: interval: %d timeout: %d\n", provider, poll_interval, timeout); if (!timeout) continue; if (!poll_interval || poll_interval > timeout) { rc = -ETIMEDOUT; break; } if (timeout > poll_interval) timeout -= poll_interval; else if (timeout == poll_interval) { timeout = 1; poll_interval = 0; } } } } if (rc == 0) dbg(ctx, "%s: scrub complete, status: \'%s\'\n", provider, buf); else dbg(ctx, "%s: error waiting for scrub completion: %s\n", provider, strerror(-rc)); if (fd) close (fd); return rc; } NDCTL_EXPORT int ndctl_bus_wait_for_scrub_completion(struct ndctl_bus *bus) { return ndctl_bus_poll_scrub_completion(bus, 0, 0); } NDCTL_EXPORT enum ndctl_fwa_state ndctl_bus_get_fw_activate_state( struct ndctl_bus *bus) { struct ndctl_ctx *ctx = ndctl_bus_get_ctx(bus); char *path = bus->bus_buf; char buf[SYSFS_ATTR_SIZE]; int len = bus->buf_len; if (bus->fwa_state == NDCTL_FWA_INVALID) return NDCTL_FWA_INVALID; if (snprintf(path, len, "%s/firmware/activate", bus->bus_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_bus_get_devname(bus)); return NDCTL_FWA_INVALID; } if (sysfs_read_attr(ctx, path, buf) < 0) return NDCTL_FWA_INVALID; bus->fwa_state = fwa_to_state(buf); return bus->fwa_state; } NDCTL_EXPORT enum ndctl_fwa_method ndctl_bus_get_fw_activate_method(struct ndctl_bus *bus) { return bus->fwa_method; } NDCTL_EXPORT int ndctl_bus_activate_firmware(struct ndctl_bus *bus, enum ndctl_fwa_method method) { struct ndctl_ctx *ctx = ndctl_bus_get_ctx(bus); char *path = bus->bus_buf; char buf[SYSFS_ATTR_SIZE]; int len = bus->buf_len; if (snprintf(path, len, "%s/firmware/activate", bus->bus_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_bus_get_devname(bus)); return -ENOMEM; } switch (method) { case NDCTL_FWA_METHOD_LIVE: case NDCTL_FWA_METHOD_SUSPEND: break; default: err(ctx, "%s: method: %d invalid\n", ndctl_bus_get_devname(bus), method); return -EINVAL; } sprintf(buf, "%s\n", method == NDCTL_FWA_METHOD_LIVE ? "live" : "quiesce"); return sysfs_write_attr(ctx, path, buf); } static int write_fw_activate_noidle(struct ndctl_bus *bus, int arg) { struct ndctl_ctx *ctx = ndctl_bus_get_ctx(bus); char *path = bus->bus_buf; char buf[SYSFS_ATTR_SIZE]; int len = bus->buf_len; if (!ndctl_bus_has_nfit(bus)) return -EOPNOTSUPP; if (snprintf(path, len, "%s/nfit/firmware_activate_noidle", bus->bus_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_bus_get_devname(bus)); return -ENOMEM; } sprintf(buf, "%d\n", arg); return sysfs_write_attr(ctx, path, buf); } NDCTL_EXPORT int ndctl_bus_set_fw_activate_noidle(struct ndctl_bus *bus) { return write_fw_activate_noidle(bus, 1); } NDCTL_EXPORT int ndctl_bus_clear_fw_activate_noidle(struct ndctl_bus *bus) { return write_fw_activate_noidle(bus, 0); } static int write_fw_activate_nosuspend(struct ndctl_bus *bus, int arg) { struct ndctl_ctx *ctx = ndctl_bus_get_ctx(bus); char *path = bus->bus_buf; char buf[SYSFS_ATTR_SIZE]; int len = bus->buf_len; if (!ndctl_bus_has_nfit(bus)) return -EOPNOTSUPP; if (snprintf(path, len, "%s/nfit/firmware_activate_nosuspend", bus->bus_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_bus_get_devname(bus)); return -ENOMEM; } sprintf(buf, "%d\n", arg); return sysfs_write_attr(ctx, path, buf); } NDCTL_EXPORT int ndctl_bus_set_fw_activate_nosuspend(struct ndctl_bus *bus) { return write_fw_activate_nosuspend(bus, 1); } NDCTL_EXPORT int ndctl_bus_clear_fw_activate_nosuspend(struct ndctl_bus *bus) { return write_fw_activate_nosuspend(bus, 0); } static enum ndctl_fwa_result fwa_result_to_result(const char *result) { if (strcmp(result, "none") == 0) return NDCTL_FWA_RESULT_NONE; if (strcmp(result, "success") == 0) return NDCTL_FWA_RESULT_SUCCESS; if (strcmp(result, "fail") == 0) return NDCTL_FWA_RESULT_FAIL; if (strcmp(result, "not_staged") == 0) return NDCTL_FWA_RESULT_NOTSTAGED; if (strcmp(result, "need_reset") == 0) return NDCTL_FWA_RESULT_NEEDRESET; return NDCTL_FWA_RESULT_INVALID; } NDCTL_EXPORT void ndctl_dimm_refresh_flags(struct ndctl_dimm *dimm) { struct ndctl_ctx *ctx = dimm->bus->ctx; char *path = dimm->dimm_buf; char buf[SYSFS_ATTR_SIZE]; /* Construct path to dimm flags sysfs file */ sprintf(path, "%s/%s/flags", dimm->dimm_path, dimm->bus_prefix); if (sysfs_read_attr(ctx, path, buf) < 0) return; /* Reset the flags */ dimm->flags.flags = 0; if (ndctl_bus_has_nfit(dimm->bus)) parse_nfit_mem_flags(dimm, buf); else if (ndctl_bus_is_papr_scm(dimm->bus)) parse_papr_flags(dimm, buf); } static int populate_cxl_dimm_attributes(struct ndctl_dimm *dimm, const char *dimm_base) { int rc = 0; char buf[SYSFS_ATTR_SIZE]; struct ndctl_ctx *ctx = dimm->bus->ctx; char *path = calloc(1, strlen(dimm_base) + 100); const char *bus_prefix = dimm->bus_prefix; if (!path) return -ENOMEM; sprintf(path, "%s/%s/id", dimm_base, bus_prefix); if (sysfs_read_attr(ctx, path, buf) == 0) { dimm->unique_id = strdup(buf); if (!dimm->unique_id) { rc = -ENOMEM; goto err_read; } } err_read: free(path); return rc; } static int populate_dimm_attributes(struct ndctl_dimm *dimm, const char *dimm_base) { int i, rc = -1; char buf[SYSFS_ATTR_SIZE]; struct ndctl_ctx *ctx = dimm->bus->ctx; char *path = calloc(1, strlen(dimm_base) + 100); const char *bus_prefix = dimm->bus_prefix; if (!path) return -ENOMEM; /* * 'unique_id' may not be available on older kernels, so don't * fail if the read fails. */ sprintf(path, "%s/%s/id", dimm_base, bus_prefix); if (sysfs_read_attr(ctx, path, buf) == 0) { unsigned int b[9]; dimm->unique_id = strdup(buf); if (!dimm->unique_id) goto err_read; if (sscanf(dimm->unique_id, "%02x%02x-%02x-%02x%02x-%02x%02x%02x%02x", &b[0], &b[1], &b[2], &b[3], &b[4], &b[5], &b[6], &b[7], &b[8]) == 9) { dimm->manufacturing_date = b[3] << 8 | b[4]; dimm->manufacturing_location = b[2]; } } sprintf(path, "%s/%s/handle", dimm_base, bus_prefix); if (sysfs_read_attr(ctx, path, buf) < 0) goto err_read; dimm->handle = strtoul(buf, NULL, 0); sprintf(path, "%s/%s/phys_id", dimm_base, bus_prefix); if (sysfs_read_attr(ctx, path, buf) < 0) goto err_read; dimm->phys_id = strtoul(buf, NULL, 0); sprintf(path, "%s/%s/serial", dimm_base, bus_prefix); if (sysfs_read_attr(ctx, path, buf) == 0) dimm->serial = strtoul(buf, NULL, 0); sprintf(path, "%s/%s/vendor", dimm_base, bus_prefix); if (sysfs_read_attr(ctx, path, buf) == 0) dimm->vendor_id = strtoul(buf, NULL, 0); sprintf(path, "%s/%s/device", dimm_base, bus_prefix); if (sysfs_read_attr(ctx, path, buf) == 0) dimm->device_id = strtoul(buf, NULL, 0); sprintf(path, "%s/%s/rev_id", dimm_base, bus_prefix); if (sysfs_read_attr(ctx, path, buf) == 0) dimm->revision_id = strtoul(buf, NULL, 0); sprintf(path, "%s/%s/dirty_shutdown", dimm_base, bus_prefix); if (sysfs_read_attr(ctx, path, buf) == 0) dimm->dirty_shutdown = strtoll(buf, NULL, 0); sprintf(path, "%s/%s/subsystem_vendor", dimm_base, bus_prefix); if (sysfs_read_attr(ctx, path, buf) == 0) dimm->subsystem_vendor_id = strtoul(buf, NULL, 0); sprintf(path, "%s/%s/subsystem_device", dimm_base, bus_prefix); if (sysfs_read_attr(ctx, path, buf) == 0) dimm->subsystem_device_id = strtoul(buf, NULL, 0); sprintf(path, "%s/%s/subsystem_rev_id", dimm_base, bus_prefix); if (sysfs_read_attr(ctx, path, buf) == 0) dimm->subsystem_revision_id = strtoul(buf, NULL, 0); sprintf(path, "%s/%s/family", dimm_base, bus_prefix); if (sysfs_read_attr(ctx, path, buf) == 0) dimm->cmd_family = strtoul(buf, NULL, 0); sprintf(path, "%s/%s/dsm_mask", dimm_base, bus_prefix); if (sysfs_read_attr(ctx, path, buf) == 0) dimm->nfit_dsm_mask = strtoul(buf, NULL, 0); sprintf(path, "%s/%s/format", dimm_base, bus_prefix); if (sysfs_read_attr(ctx, path, buf) == 0) dimm->format[0] = strtoul(buf, NULL, 0); for (i = 1; i < dimm->formats; i++) { sprintf(path, "%s/%s/format%d", dimm_base, bus_prefix, i); if (sysfs_read_attr(ctx, path, buf) == 0) dimm->format[i] = strtoul(buf, NULL, 0); } sprintf(path, "%s/%s/flags", dimm_base, bus_prefix); dimm->health_eventfd = open(path, O_RDONLY|O_CLOEXEC); ndctl_dimm_refresh_flags(dimm); rc = 0; err_read: free(path); return rc; } static int add_papr_dimm(struct ndctl_dimm *dimm, const char *dimm_base) { int rc = -ENODEV; char buf[SYSFS_ATTR_SIZE]; struct ndctl_ctx *ctx = dimm->bus->ctx; char *path = calloc(1, strlen(dimm_base) + 100); const char * const devname = ndctl_dimm_get_devname(dimm); dbg(ctx, "%s: Probing of_pmem dimm at %s\n", devname, dimm_base); if (!path) return -ENOMEM; /* Check the compatibility of the probed nvdimm */ sprintf(path, "%s/../of_node/compatible", dimm_base); if (sysfs_read_attr(ctx, path, buf) < 0) { dbg(ctx, "%s: Unable to read compatible field\n", devname); rc = -ENODEV; goto out; } dbg(ctx, "%s:Compatible of_pmem = '%s'\n", devname, buf); /* Probe for papr-scm memory */ if (strcmp(buf, "ibm,pmemory") == 0) { /* Read the dimm flags file */ sprintf(path, "%s/papr/flags", dimm_base); if (sysfs_read_attr(ctx, path, buf) < 0) { rc = -errno; err(ctx, "%s: Unable to read dimm-flags\n", devname); goto out; } dbg(ctx, "%s: Adding papr-scm dimm flags:\"%s\"\n", devname, buf); dimm->cmd_family = NVDIMM_FAMILY_PAPR; /* Parse dimm flags */ parse_papr_flags(dimm, buf); /* Allocate monitor mode fd */ dimm->health_eventfd = open(path, O_RDONLY|O_CLOEXEC); /* Get the dirty shutdown counter value */ sprintf(path, "%s/papr/dirty_shutdown", dimm_base); if (sysfs_read_attr(ctx, path, buf) == 0) dimm->dirty_shutdown = strtoll(buf, NULL, 0); rc = 0; } else if (strcmp(buf, "nvdimm_test") == 0) { dimm->cmd_family = NVDIMM_FAMILY_PAPR; /* probe via common populate_dimm_attributes() */ rc = populate_dimm_attributes(dimm, dimm_base); } out: free(path); return rc; } static void *add_dimm(void *parent, int id, const char *dimm_base) { int formats, i, rc = -ENODEV; struct ndctl_dimm *dimm = NULL; char buf[SYSFS_ATTR_SIZE]; struct ndctl_bus *bus = parent; struct ndctl_ctx *ctx = bus->ctx; char *path = calloc(1, strlen(dimm_base) + 100); if (!path) return NULL; sprintf(path, "%s/%s/formats", dimm_base, ndctl_bus_has_nfit(bus) ? "nfit" : "papr"); if (sysfs_read_attr(ctx, path, buf) < 0) formats = 1; else formats = clamp(strtoul(buf, NULL, 0), 1UL, 2UL); dimm = calloc(1, sizeof(*dimm) + sizeof(int) * formats); if (!dimm) goto err_dimm; dimm->bus = bus; dimm->id = id; sprintf(path, "%s/dev", dimm_base); if (sysfs_read_attr(ctx, path, buf) < 0) goto err_read; if (sscanf(buf, "%d:%d", &dimm->major, &dimm->minor) != 2) goto err_read; sprintf(path, "%s/commands", dimm_base); if (sysfs_read_attr(ctx, path, buf) < 0) goto err_read; dimm->cmd_mask = parse_commands(buf, 1); dimm->dimm_buf = calloc(1, strlen(dimm_base) + 50); if (!dimm->dimm_buf) goto err_read; dimm->buf_len = strlen(dimm_base) + 50; dimm->dimm_path = strdup(dimm_base); if (!dimm->dimm_path) goto err_read; sprintf(path, "%s/modalias", dimm_base); if (sysfs_read_attr(ctx, path, buf) < 0) goto err_read; dimm->module = util_modalias_to_module(ctx, buf); dimm->handle = -1; dimm->phys_id = -1; dimm->serial = -1; dimm->vendor_id = -1; dimm->device_id = -1; dimm->revision_id = -1; dimm->health_eventfd = -1; dimm->dirty_shutdown = -ENOENT; dimm->subsystem_vendor_id = -1; dimm->subsystem_device_id = -1; dimm->subsystem_revision_id = -1; dimm->manufacturing_date = -1; dimm->manufacturing_location = -1; dimm->cmd_family = -1; dimm->nfit_dsm_mask = ULONG_MAX; for (i = 0; i < formats; i++) dimm->format[i] = -1; sprintf(path, "%s/flags", dimm_base); if (sysfs_read_attr(ctx, path, buf) < 0) { dimm->locked = -1; dimm->aliased = -1; } else parse_dimm_flags(dimm, buf); sprintf(path, "%s/firmware/activate", dimm_base); if (sysfs_read_attr(ctx, path, buf) < 0) dimm->fwa_state = NDCTL_FWA_INVALID; else dimm->fwa_state = fwa_to_state(buf); sprintf(path, "%s/firmware/result", dimm_base); if (sysfs_read_attr(ctx, path, buf) < 0) dimm->fwa_result = NDCTL_FWA_RESULT_INVALID; else dimm->fwa_result = fwa_result_to_result(buf); dimm->formats = formats; /* Check if the given dimm supports nfit */ if (ndctl_bus_has_nfit(bus)) { dimm->bus_prefix = strdup("nfit"); if (!dimm->bus_prefix) { rc = -ENOMEM; goto out; } rc = populate_dimm_attributes(dimm, dimm_base); } else if (ndctl_bus_has_of_node(bus)) { dimm->bus_prefix = strdup("papr"); if (!dimm->bus_prefix) { rc = -ENOMEM; goto out; } rc = add_papr_dimm(dimm, dimm_base); } else if (ndctl_bus_has_cxl(bus)) { dimm->bus_prefix = strdup("cxl"); if (!dimm->bus_prefix) { rc = -ENOMEM; goto out; } rc = populate_cxl_dimm_attributes(dimm, dimm_base); } if (rc == -ENODEV) { /* Unprobed dimm with no family */ rc = 0; goto out; } /* Assign dimm-ops based on command family */ if (dimm->cmd_family == NVDIMM_FAMILY_INTEL) dimm->ops = intel_dimm_ops; if (dimm->cmd_family == NVDIMM_FAMILY_HPE1) dimm->ops = hpe1_dimm_ops; if (dimm->cmd_family == NVDIMM_FAMILY_MSFT) dimm->ops = msft_dimm_ops; if (dimm->cmd_family == NVDIMM_FAMILY_HYPERV) dimm->ops = hyperv_dimm_ops; if (dimm->cmd_family == NVDIMM_FAMILY_PAPR) dimm->ops = papr_dimm_ops; out: if (rc) { err(ctx, "%s: probe failed: %s\n", ndctl_dimm_get_devname(dimm), strerror(-rc)); goto err_read; } list_add(&bus->dimms, &dimm->list); free(path); return dimm; err_read: free_dimm(dimm); err_dimm: free(path); return NULL; } static void dimms_init(struct ndctl_bus *bus) { if (bus->dimms_init) return; bus->dimms_init = 1; device_parse(bus->ctx, bus, bus->bus_path, "nmem", bus, add_dimm); } NDCTL_EXPORT struct ndctl_dimm *ndctl_dimm_get_first(struct ndctl_bus *bus) { dimms_init(bus); return list_top(&bus->dimms, struct ndctl_dimm, list); } NDCTL_EXPORT struct ndctl_dimm *ndctl_dimm_get_next(struct ndctl_dimm *dimm) { struct ndctl_bus *bus = dimm->bus; return list_next(&bus->dimms, dimm, list); } NDCTL_EXPORT unsigned int ndctl_dimm_get_handle(struct ndctl_dimm *dimm) { return dimm->handle; } NDCTL_EXPORT unsigned short ndctl_dimm_get_phys_id(struct ndctl_dimm *dimm) { return dimm->phys_id; } NDCTL_EXPORT unsigned short ndctl_dimm_get_vendor(struct ndctl_dimm *dimm) { return dimm->vendor_id; } NDCTL_EXPORT unsigned short ndctl_dimm_get_device(struct ndctl_dimm *dimm) { return dimm->device_id; } NDCTL_EXPORT unsigned short ndctl_dimm_get_revision(struct ndctl_dimm *dimm) { return dimm->revision_id; } NDCTL_EXPORT long long ndctl_dimm_get_dirty_shutdown(struct ndctl_dimm *dimm) { return dimm->dirty_shutdown; } NDCTL_EXPORT unsigned short ndctl_dimm_get_subsystem_vendor( struct ndctl_dimm *dimm) { return dimm->subsystem_vendor_id; } NDCTL_EXPORT unsigned short ndctl_dimm_get_subsystem_device( struct ndctl_dimm *dimm) { return dimm->subsystem_device_id; } NDCTL_EXPORT unsigned short ndctl_dimm_get_subsystem_revision( struct ndctl_dimm *dimm) { return dimm->subsystem_revision_id; } NDCTL_EXPORT unsigned short ndctl_dimm_get_manufacturing_date( struct ndctl_dimm *dimm) { return dimm->manufacturing_date; } NDCTL_EXPORT unsigned char ndctl_dimm_get_manufacturing_location( struct ndctl_dimm *dimm) { return dimm->manufacturing_location; } NDCTL_EXPORT unsigned short ndctl_dimm_get_format(struct ndctl_dimm *dimm) { return dimm->format[0]; } NDCTL_EXPORT int ndctl_dimm_get_formats(struct ndctl_dimm *dimm) { return dimm->formats; } NDCTL_EXPORT int ndctl_dimm_get_formatN(struct ndctl_dimm *dimm, int i) { if (i < dimm->formats && i >= 0) return dimm->format[i]; return -EINVAL; } NDCTL_EXPORT unsigned int ndctl_dimm_get_major(struct ndctl_dimm *dimm) { return dimm->major; } NDCTL_EXPORT unsigned int ndctl_dimm_get_minor(struct ndctl_dimm *dimm) { return dimm->minor; } NDCTL_EXPORT unsigned int ndctl_dimm_get_id(struct ndctl_dimm *dimm) { return dimm->id; } NDCTL_EXPORT const char *ndctl_dimm_get_unique_id(struct ndctl_dimm *dimm) { return dimm->unique_id; } NDCTL_EXPORT unsigned int ndctl_dimm_get_serial(struct ndctl_dimm *dimm) { return dimm->serial; } NDCTL_EXPORT const char *ndctl_dimm_get_devname(struct ndctl_dimm *dimm) { return devpath_to_devname(dimm->dimm_path); } NDCTL_EXPORT const char *ndctl_dimm_get_cmd_name(struct ndctl_dimm *dimm, int cmd) { return nvdimm_cmd_name(cmd); } NDCTL_EXPORT int ndctl_dimm_has_errors(struct ndctl_dimm *dimm) { union dimm_flags flags = dimm->flags; flags.f_notify = 0; return flags.flags != 0; } NDCTL_EXPORT int ndctl_dimm_locked(struct ndctl_dimm *dimm) { return dimm->locked; } NDCTL_EXPORT int ndctl_dimm_aliased(struct ndctl_dimm *dimm) { return dimm->aliased; } NDCTL_EXPORT int ndctl_dimm_has_notifications(struct ndctl_dimm *dimm) { return dimm->flags.f_notify; } NDCTL_EXPORT int ndctl_dimm_failed_save(struct ndctl_dimm *dimm) { return dimm->flags.f_save; } NDCTL_EXPORT int ndctl_dimm_failed_arm(struct ndctl_dimm *dimm) { return dimm->flags.f_arm; } NDCTL_EXPORT int ndctl_dimm_failed_restore(struct ndctl_dimm *dimm) { return dimm->flags.f_restore; } NDCTL_EXPORT int ndctl_dimm_smart_pending(struct ndctl_dimm *dimm) { return dimm->flags.f_smart; } NDCTL_EXPORT int ndctl_dimm_failed_flush(struct ndctl_dimm *dimm) { return dimm->flags.f_flush; } NDCTL_EXPORT int ndctl_dimm_failed_map(struct ndctl_dimm *dimm) { return dimm->flags.f_map; } NDCTL_EXPORT int ndctl_dimm_is_cmd_supported(struct ndctl_dimm *dimm, int cmd) { struct ndctl_dimm_ops *ops = dimm->ops; if (ops && ops->cmd_is_supported) return ops->cmd_is_supported(dimm, cmd); return !!(dimm->cmd_mask & (1ULL << cmd)); } NDCTL_EXPORT int ndctl_dimm_get_health_eventfd(struct ndctl_dimm *dimm) { return dimm->health_eventfd; } NDCTL_EXPORT unsigned int ndctl_dimm_get_health(struct ndctl_dimm *dimm) { struct ndctl_cmd *cmd = NULL; unsigned int health; struct ndctl_ctx *ctx = ndctl_dimm_get_ctx(dimm); const char *devname = ndctl_dimm_get_devname(dimm); int rc; cmd = ndctl_dimm_cmd_new_smart(dimm); if (!cmd) { err(ctx, "%s: no smart command support\n", devname); return UINT_MAX; } rc = ndctl_cmd_submit(cmd); if (rc) { err(ctx, "%s: smart command failed\n", devname); ndctl_cmd_unref(cmd); if (rc < 0) errno = -rc; return UINT_MAX; } health = ndctl_cmd_smart_get_health(cmd); ndctl_cmd_unref(cmd); return health; } NDCTL_EXPORT unsigned int ndctl_dimm_get_flags(struct ndctl_dimm *dimm) { struct ndctl_cmd *cmd = NULL; unsigned int flags; struct ndctl_ctx *ctx = ndctl_dimm_get_ctx(dimm); const char *devname = ndctl_dimm_get_devname(dimm); int rc; cmd = ndctl_dimm_cmd_new_smart(dimm); if (!cmd) { dbg(ctx, "%s: no smart command support\n", devname); return UINT_MAX; } rc = ndctl_cmd_submit(cmd); if (rc) { dbg(ctx, "%s: smart command failed\n", devname); ndctl_cmd_unref(cmd); if (rc < 0) errno = -rc; return UINT_MAX; } flags = ndctl_cmd_smart_get_flags(cmd); ndctl_cmd_unref(cmd); return flags; } NDCTL_EXPORT int ndctl_dimm_is_flag_supported(struct ndctl_dimm *dimm, unsigned int flag) { unsigned int flags = ndctl_dimm_get_flags(dimm); return (flags == UINT_MAX) ? 0 : !!(flags & flag); } NDCTL_EXPORT unsigned int ndctl_dimm_get_event_flags(struct ndctl_dimm *dimm) { int rc; struct ndctl_cmd *cmd = NULL; unsigned int alarm_flags, event_flags = 0; struct ndctl_ctx *ctx = ndctl_dimm_get_ctx(dimm); const char *devname = ndctl_dimm_get_devname(dimm); cmd = ndctl_dimm_cmd_new_smart(dimm); if (!cmd) { err(ctx, "%s: no smart command support\n", devname); return UINT_MAX; } rc = ndctl_cmd_submit(cmd); if (rc) { err(ctx, "%s: smart command failed\n", devname); ndctl_cmd_unref(cmd); if (rc < 0) errno = -rc; return UINT_MAX; } alarm_flags = ndctl_cmd_smart_get_alarm_flags(cmd); if (alarm_flags & ND_SMART_SPARE_TRIP) event_flags |= ND_EVENT_SPARES_REMAINING; if (alarm_flags & ND_SMART_MTEMP_TRIP) event_flags |= ND_EVENT_MEDIA_TEMPERATURE; if (alarm_flags & ND_SMART_CTEMP_TRIP) event_flags |= ND_EVENT_CTRL_TEMPERATURE; if (ndctl_cmd_smart_get_shutdown_state(cmd)) event_flags |= ND_EVENT_UNCLEAN_SHUTDOWN; ndctl_cmd_unref(cmd); return event_flags; } NDCTL_EXPORT unsigned int ndctl_dimm_handle_get_node(struct ndctl_dimm *dimm) { return dimm->handle >> 16 & 0xfff; } NDCTL_EXPORT unsigned int ndctl_dimm_handle_get_socket(struct ndctl_dimm *dimm) { return dimm->handle >> 12 & 0xf; } NDCTL_EXPORT unsigned int ndctl_dimm_handle_get_imc(struct ndctl_dimm *dimm) { return dimm->handle >> 8 & 0xf; } NDCTL_EXPORT unsigned int ndctl_dimm_handle_get_channel(struct ndctl_dimm *dimm) { return dimm->handle >> 4 & 0xf; } NDCTL_EXPORT unsigned int ndctl_dimm_handle_get_dimm(struct ndctl_dimm *dimm) { return dimm->handle & 0xf; } NDCTL_EXPORT struct ndctl_bus *ndctl_dimm_get_bus(struct ndctl_dimm *dimm) { return dimm->bus; } NDCTL_EXPORT struct ndctl_ctx *ndctl_dimm_get_ctx(struct ndctl_dimm *dimm) { return dimm->bus->ctx; } NDCTL_EXPORT int ndctl_dimm_disable(struct ndctl_dimm *dimm) { struct ndctl_ctx *ctx = ndctl_dimm_get_ctx(dimm); const char *devname = ndctl_dimm_get_devname(dimm); if (!ndctl_dimm_is_enabled(dimm)) return 0; util_unbind(dimm->dimm_path, ctx); if (ndctl_dimm_is_enabled(dimm)) { err(ctx, "%s: failed to disable\n", devname); return -EBUSY; } dbg(ctx, "%s: disabled\n", devname); return 0; } NDCTL_EXPORT int ndctl_dimm_enable(struct ndctl_dimm *dimm) { const char *devname = ndctl_dimm_get_devname(dimm); struct ndctl_ctx *ctx = ndctl_dimm_get_ctx(dimm); if (ndctl_dimm_is_enabled(dimm)) return 0; util_bind(devname, dimm->module, "nd", ctx); if (!ndctl_dimm_is_enabled(dimm)) { err(ctx, "%s: failed to enable\n", devname); return -ENXIO; } dbg(ctx, "%s: enabled\n", devname); return 0; } static int dimm_set_arm(struct ndctl_dimm *dimm, bool arm) { struct ndctl_ctx *ctx = ndctl_dimm_get_ctx(dimm); char *path = dimm->dimm_buf; int len = dimm->buf_len; if (dimm->fwa_state == NDCTL_FWA_INVALID) return NDCTL_FWA_INVALID; if (snprintf(path, len, "%s/firmware/activate", dimm->dimm_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_dimm_get_devname(dimm)); return NDCTL_FWA_INVALID; } if (sysfs_write_attr(ctx, path, arm ? "arm" : "disarm") < 0) return NDCTL_FWA_INVALID; return NDCTL_FWA_ARMED; } NDCTL_EXPORT enum ndctl_fwa_state ndctl_dimm_fw_activate_disarm( struct ndctl_dimm *dimm) { return dimm_set_arm(dimm, false); } NDCTL_EXPORT enum ndctl_fwa_state ndctl_dimm_fw_activate_arm( struct ndctl_dimm *dimm) { return dimm_set_arm(dimm, true); } NDCTL_EXPORT enum ndctl_fwa_state ndctl_dimm_get_fw_activate_state( struct ndctl_dimm *dimm) { struct ndctl_ctx *ctx = ndctl_dimm_get_ctx(dimm); char *path = dimm->dimm_buf; char buf[SYSFS_ATTR_SIZE]; int len = dimm->buf_len; if (dimm->fwa_state == NDCTL_FWA_INVALID) return NDCTL_FWA_INVALID; if (snprintf(path, len, "%s/firmware/activate", dimm->dimm_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_dimm_get_devname(dimm)); return NDCTL_FWA_INVALID; } if (sysfs_read_attr(ctx, path, buf) < 0) return NDCTL_FWA_INVALID; dimm->fwa_state = fwa_to_state(buf); return dimm->fwa_state; } NDCTL_EXPORT enum ndctl_fwa_result ndctl_dimm_get_fw_activate_result( struct ndctl_dimm *dimm) { struct ndctl_ctx *ctx = ndctl_dimm_get_ctx(dimm); char *path = dimm->dimm_buf; char buf[SYSFS_ATTR_SIZE]; int len = dimm->buf_len; if (dimm->fwa_result == NDCTL_FWA_RESULT_INVALID) return NDCTL_FWA_RESULT_INVALID; if (snprintf(path, len, "%s/firmware/result", dimm->dimm_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_dimm_get_devname(dimm)); return NDCTL_FWA_RESULT_INVALID; } if (sysfs_read_attr(ctx, path, buf) < 0) return NDCTL_FWA_RESULT_INVALID; return fwa_result_to_result(buf); } NDCTL_EXPORT struct ndctl_dimm *ndctl_dimm_get_by_handle(struct ndctl_bus *bus, unsigned int handle) { struct ndctl_dimm *dimm; ndctl_dimm_foreach(bus, dimm) if (dimm->handle == handle) return dimm; return NULL; } static struct ndctl_dimm *ndctl_dimm_get_by_id(struct ndctl_bus *bus, unsigned int id) { struct ndctl_dimm *dimm; ndctl_dimm_foreach(bus, dimm) if (ndctl_dimm_get_id(dimm) == id) return dimm; return NULL; } /** * ndctl_bus_get_region_by_physical_address - get region by physical address * @bus: ndctl_bus instance * @address: (System) Physical Address * * If @bus and @address is valid, returns a region address, which * physical address belongs to. */ NDCTL_EXPORT struct ndctl_region *ndctl_bus_get_region_by_physical_address( struct ndctl_bus *bus, unsigned long long address) { unsigned long long region_start, region_end; struct ndctl_region *region; ndctl_region_foreach(bus, region) { region_start = ndctl_region_get_resource(region); region_end = region_start + ndctl_region_get_size(region); if (region_start <= address && address < region_end) return region; } return NULL; } /** * ndctl_bus_get_dimm_by_physical_address - get ndctl_dimm pointer by physical address * @bus: ndctl_bus instance * @address: (System) Physical Address * * Returns address of ndctl_dimm on success. */ NDCTL_EXPORT struct ndctl_dimm *ndctl_bus_get_dimm_by_physical_address( struct ndctl_bus *bus, unsigned long long address) { unsigned int handle; unsigned long long dpa; struct ndctl_region *region; if (!bus) return NULL; region = ndctl_bus_get_region_by_physical_address(bus, address); if (!region) return NULL; if (ndctl_region_get_interleave_ways(region) == 1) { struct ndctl_mapping *mapping = ndctl_mapping_get_first(region); /* No need to ask firmware, there's only one dimm */ if (!mapping) return NULL; return ndctl_mapping_get_dimm(mapping); } /* * Since the region is interleaved, we need to ask firmware about it. * If it supports Translate SPA, the dimm is returned. */ if (ndctl_bus_has_nfit(bus)) { int rc; rc = ndctl_bus_nfit_translate_spa(bus, address, &handle, &dpa); if (rc) return NULL; return ndctl_dimm_get_by_handle(bus, handle); } /* No way to get dimm info */ return NULL; } static int region_set_type(struct ndctl_region *region, char *path) { struct ndctl_ctx *ctx = ndctl_region_get_ctx(region); char buf[SYSFS_ATTR_SIZE]; int rc; sprintf(path, "%s/nstype", region->region_path); rc = sysfs_read_attr(ctx, path, buf); if (rc < 0) return rc; region->nstype = strtoul(buf, NULL, 0); sprintf(path, "%s/set_cookie", region->region_path); if (region->nstype == ND_DEVICE_NAMESPACE_PMEM) { rc = sysfs_read_attr(ctx, path, buf); if (rc < 0) return rc; region->iset.cookie = strtoull(buf, NULL, 0); dbg(ctx, "%s: iset-%#.16llx added\n", ndctl_region_get_devname(region), region->iset.cookie); } return 0; } static enum ndctl_persistence_domain region_get_pd_type(char *name) { if (strncmp("cpu_cache", name, 9) == 0) return PERSISTENCE_CPU_CACHE; else if (strncmp("memory_controller", name, 17) == 0) return PERSISTENCE_MEM_CTRL; else if (strncmp("none", name, 4) == 0) return PERSISTENCE_NONE; else return PERSISTENCE_UNKNOWN; } static void *add_region(void *parent, int id, const char *region_base) { char buf[SYSFS_ATTR_SIZE]; struct ndctl_region *region; struct ndctl_bus *bus = parent; struct ndctl_ctx *ctx = bus->ctx; char *path = calloc(1, strlen(region_base) + 100); int perm, rc; if (!path) return NULL; region = calloc(1, sizeof(*region)); if (!region) goto err_region; list_head_init(®ion->btts); list_head_init(®ion->pfns); list_head_init(®ion->daxs); list_head_init(®ion->stale_btts); list_head_init(®ion->stale_pfns); list_head_init(®ion->stale_daxs); list_head_init(®ion->mappings); list_head_init(®ion->namespaces); list_head_init(®ion->stale_namespaces); region->region_path = (char *) region_base; region->bus = bus; region->id = id; sprintf(path, "%s/size", region_base); if (sysfs_read_attr(ctx, path, buf) < 0) goto err_read; region->size = strtoull(buf, NULL, 0); sprintf(path, "%s/mappings", region_base); if (sysfs_read_attr(ctx, path, buf) < 0) goto err_read; region->num_mappings = strtoul(buf, NULL, 0); sprintf(path, "%s/%s/range_index", region_base, ndctl_bus_has_nfit(bus) ? "nfit": "papr"); if (sysfs_read_attr(ctx, path, buf) < 0) region->range_index = -1; else region->range_index = strtoul(buf, NULL, 0); sprintf(path, "%s/read_only", region_base); if (sysfs_read_attr(ctx, path, buf) < 0) goto err_read; region->ro = strtoul(buf, NULL, 0); sprintf(path, "%s/modalias", region_base); if (sysfs_read_attr(ctx, path, buf) < 0) goto err_read; region->module = util_modalias_to_module(ctx, buf); sprintf(path, "%s/numa_node", region_base); if ((rc = sysfs_read_attr(ctx, path, buf)) == 0) region->numa_node = strtol(buf, NULL, 0); else if (rc == -ENOENT) region->numa_node = NUMA_NO_ATTR; else region->numa_node = NUMA_NO_NODE; sprintf(path, "%s/target_node", region_base); if (sysfs_read_attr(ctx, path, buf) == 0) region->target_node = strtol(buf, NULL, 0); else region->target_node = -1; sprintf(path, "%s/align", region_base); if (sysfs_read_attr(ctx, path, buf) == 0) region->align = strtoul(buf, NULL, 0); else region->align = ULONG_MAX; if (region_set_type(region, path) < 0) goto err_read; region->region_buf = calloc(1, strlen(region_base) + 50); if (!region->region_buf) goto err_read; region->buf_len = strlen(region_base) + 50; region->region_path = strdup(region_base); if (!region->region_path) goto err_read; list_add(&bus->regions, ®ion->list); /* get the persistence domain attrib */ sprintf(path, "%s/persistence_domain", region_base); if (sysfs_read_attr(ctx, path, buf) < 0) region->persistence_domain = PERSISTENCE_UNKNOWN; else region->persistence_domain = region_get_pd_type(buf); sprintf(path, "%s/deep_flush", region_base); region->flush_fd = open(path, O_RDWR | O_CLOEXEC); if (region->flush_fd == -1) goto out; if (pread(region->flush_fd, buf, 1, 0) == -1) { close(region->flush_fd); region->flush_fd = -1; goto out; } /* pread() doesn't add NUL termination */ buf[1] = 0; perm = strtol(buf, NULL, 0); if (perm == 0) { close(region->flush_fd); region->flush_fd = -1; } out: free(path); return region; err_read: free(region->region_buf); free(region); err_region: free(path); return NULL; } static void regions_init(struct ndctl_bus *bus) { if (bus->regions_init) return; bus->regions_init = 1; device_parse(bus->ctx, bus, bus->bus_path, "region", bus, add_region); } NDCTL_EXPORT struct ndctl_region *ndctl_region_get_first(struct ndctl_bus *bus) { regions_init(bus); return list_top(&bus->regions, struct ndctl_region, list); } NDCTL_EXPORT struct ndctl_region *ndctl_region_get_next(struct ndctl_region *region) { struct ndctl_bus *bus = region->bus; return list_next(&bus->regions, region, list); } NDCTL_EXPORT unsigned int ndctl_region_get_id(struct ndctl_region *region) { return region->id; } NDCTL_EXPORT unsigned int ndctl_region_get_interleave_ways(struct ndctl_region *region) { return max(1U, ndctl_region_get_mappings(region)); } NDCTL_EXPORT unsigned int ndctl_region_get_mappings(struct ndctl_region *region) { return region->num_mappings; } NDCTL_EXPORT unsigned long long ndctl_region_get_size(struct ndctl_region *region) { return region->size; } NDCTL_EXPORT unsigned long long ndctl_region_get_available_size( struct ndctl_region *region) { unsigned int nstype = ndctl_region_get_nstype(region); struct ndctl_ctx *ctx = ndctl_region_get_ctx(region); char *path = region->region_buf; int rc, len = region->buf_len; char buf[SYSFS_ATTR_SIZE]; switch (nstype) { case ND_DEVICE_NAMESPACE_PMEM: case ND_DEVICE_NAMESPACE_BLK: break; default: return 0; } if (snprintf(path, len, "%s/available_size", region->region_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_region_get_devname(region)); errno = ENOMEM; return ULLONG_MAX; } rc = sysfs_read_attr(ctx, path, buf); if (rc < 0) { errno = -rc; return ULLONG_MAX; } return strtoull(buf, NULL, 0); } NDCTL_EXPORT unsigned long long ndctl_region_get_max_available_extent( struct ndctl_region *region) { unsigned int nstype = ndctl_region_get_nstype(region); struct ndctl_ctx *ctx = ndctl_region_get_ctx(region); char *path = region->region_buf; int rc, len = region->buf_len; char buf[SYSFS_ATTR_SIZE]; switch (nstype) { case ND_DEVICE_NAMESPACE_PMEM: case ND_DEVICE_NAMESPACE_BLK: break; default: return 0; } if (snprintf(path, len, "%s/max_available_extent", region->region_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_region_get_devname(region)); errno = ENOMEM; return ULLONG_MAX; } /* fall back to legacy behavior if max extents is not exported */ rc = sysfs_read_attr(ctx, path, buf); if (rc < 0) { dbg(ctx, "max extents attribute not exported on older kernels\n"); errno = -rc; return ULLONG_MAX; } return strtoull(buf, NULL, 0); } NDCTL_EXPORT unsigned int ndctl_region_get_range_index(struct ndctl_region *region) { return region->range_index; } NDCTL_EXPORT unsigned int ndctl_region_get_nstype(struct ndctl_region *region) { return region->nstype; } NDCTL_EXPORT unsigned int ndctl_region_get_type(struct ndctl_region *region) { switch (region->nstype) { case ND_DEVICE_NAMESPACE_IO: case ND_DEVICE_NAMESPACE_PMEM: return ND_DEVICE_REGION_PMEM; default: return ND_DEVICE_REGION_BLK; } } NDCTL_EXPORT struct ndctl_namespace *ndctl_region_get_namespace_seed( struct ndctl_region *region) { struct ndctl_bus *bus = ndctl_region_get_bus(region); struct ndctl_ctx *ctx = ndctl_bus_get_ctx(bus); char *path = region->region_buf; struct ndctl_namespace *ndns; int len = region->buf_len; char buf[SYSFS_ATTR_SIZE]; if (snprintf(path, len, "%s/namespace_seed", region->region_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_region_get_devname(region)); return NULL; } if (sysfs_read_attr(ctx, path, buf) < 0) return NULL; ndctl_namespace_foreach(region, ndns) if (strcmp(buf, ndctl_namespace_get_devname(ndns)) == 0) return ndns; return NULL; } static const char *ndctl_device_type_name(int type) { switch (type) { case ND_DEVICE_DIMM: return "dimm"; case ND_DEVICE_REGION_PMEM: return "pmem"; case ND_DEVICE_REGION_BLK: return "blk"; case ND_DEVICE_NAMESPACE_IO: return "namespace_io"; case ND_DEVICE_NAMESPACE_PMEM: return "namespace_pmem"; case ND_DEVICE_NAMESPACE_BLK: return "namespace_blk"; case ND_DEVICE_DAX_PMEM: return "dax_pmem"; default: return "unknown"; } } NDCTL_EXPORT const char *ndctl_region_get_type_name(struct ndctl_region *region) { return ndctl_device_type_name(ndctl_region_get_type(region)); } NDCTL_EXPORT struct ndctl_bus *ndctl_region_get_bus(struct ndctl_region *region) { return region->bus; } NDCTL_EXPORT struct ndctl_ctx *ndctl_region_get_ctx(struct ndctl_region *region) { return region->bus->ctx; } NDCTL_EXPORT struct ndctl_dimm *ndctl_region_get_first_dimm(struct ndctl_region *region) { struct ndctl_bus *bus = region->bus; struct ndctl_dimm *dimm; ndctl_dimm_foreach(bus, dimm) { struct ndctl_mapping *mapping; ndctl_mapping_foreach(region, mapping) if (mapping->dimm == dimm) return dimm; } return NULL; } NDCTL_EXPORT struct ndctl_dimm *ndctl_region_get_next_dimm(struct ndctl_region *region, struct ndctl_dimm *dimm) { while ((dimm = ndctl_dimm_get_next(dimm))) { struct ndctl_mapping *mapping; ndctl_mapping_foreach(region, mapping) if (mapping->dimm == dimm) return dimm; } return NULL; } NDCTL_EXPORT int ndctl_region_has_numa(struct ndctl_region *region) { return (region->numa_node != NUMA_NO_ATTR); } NDCTL_EXPORT int ndctl_region_get_numa_node(struct ndctl_region *region) { return region->numa_node; } NDCTL_EXPORT int ndctl_region_get_target_node(struct ndctl_region *region) { return region->target_node; } NDCTL_EXPORT struct badblock *ndctl_region_get_next_badblock(struct ndctl_region *region) { return badblocks_iter_next(®ion->bb_iter); } NDCTL_EXPORT struct badblock *ndctl_region_get_first_badblock(struct ndctl_region *region) { return badblocks_iter_first(®ion->bb_iter, ndctl_region_get_ctx(region), region->region_path); } NDCTL_EXPORT enum ndctl_persistence_domain ndctl_region_get_persistence_domain(struct ndctl_region *region) { return region->persistence_domain; } static struct nd_cmd_vendor_tail *to_vendor_tail(struct ndctl_cmd *cmd) { struct nd_cmd_vendor_tail *tail = (struct nd_cmd_vendor_tail *) (cmd->cmd_buf + sizeof(struct nd_cmd_vendor_hdr) + cmd->vendor->in_length); return tail; } static u32 cmd_get_firmware_status(struct ndctl_cmd *cmd) { switch (cmd->type) { case ND_CMD_VENDOR: return to_vendor_tail(cmd)->status; case ND_CMD_GET_CONFIG_SIZE: return cmd->get_size->status; case ND_CMD_GET_CONFIG_DATA: return cmd->get_data->status; case ND_CMD_SET_CONFIG_DATA: return *(u32 *) (cmd->cmd_buf + sizeof(struct nd_cmd_set_config_hdr) + cmd->iter.max_xfer); } return -1U; } static void cmd_set_xfer(struct ndctl_cmd *cmd, u32 xfer) { if (cmd->type == ND_CMD_GET_CONFIG_DATA) cmd->get_data->in_length = xfer; else cmd->set_data->in_length = xfer; } static u32 cmd_get_xfer(struct ndctl_cmd *cmd) { if (cmd->type == ND_CMD_GET_CONFIG_DATA) return cmd->get_data->in_length; return cmd->set_data->in_length; } static void cmd_set_offset(struct ndctl_cmd *cmd, u32 offset) { if (cmd->type == ND_CMD_GET_CONFIG_DATA) cmd->get_data->in_offset = offset; else cmd->set_data->in_offset = offset; } static u32 cmd_get_offset(struct ndctl_cmd *cmd) { if (cmd->type == ND_CMD_GET_CONFIG_DATA) return cmd->get_data->in_offset; return cmd->set_data->in_offset; } NDCTL_EXPORT struct ndctl_cmd *ndctl_dimm_cmd_new_vendor_specific( struct ndctl_dimm *dimm, unsigned int opcode, size_t input_size, size_t output_size) { struct ndctl_bus *bus = ndctl_dimm_get_bus(dimm); struct ndctl_ctx *ctx = ndctl_bus_get_ctx(bus); struct ndctl_cmd *cmd; size_t size; if (!ndctl_dimm_is_cmd_supported(dimm, ND_CMD_VENDOR)) { dbg(ctx, "unsupported cmd\n"); return NULL; } size = sizeof(*cmd) + sizeof(struct nd_cmd_vendor_hdr) + sizeof(struct nd_cmd_vendor_tail) + input_size + output_size; cmd = calloc(1, size); if (!cmd) return NULL; cmd->dimm = dimm; ndctl_cmd_ref(cmd); cmd->type = ND_CMD_VENDOR; cmd->size = size; cmd->status = 1; cmd->vendor->opcode = opcode; cmd->vendor->in_length = input_size; cmd->get_firmware_status = cmd_get_firmware_status; to_vendor_tail(cmd)->out_length = output_size; return cmd; } NDCTL_EXPORT ssize_t ndctl_cmd_vendor_set_input(struct ndctl_cmd *cmd, void *buf, unsigned int len) { if (cmd->type != ND_CMD_VENDOR) return -EINVAL; len = min(len, cmd->vendor->in_length); memcpy(cmd->vendor->in_buf, buf, len); return len; } NDCTL_EXPORT ssize_t ndctl_cmd_vendor_get_output_size(struct ndctl_cmd *cmd) { if (cmd->type != ND_CMD_VENDOR) return -EINVAL; /* * When cmd->status is non-zero it contains either a negative * error code, or the number of bytes that are available in the * output buffer. */ if (cmd->status) return cmd->status; return to_vendor_tail(cmd)->out_length; } NDCTL_EXPORT ssize_t ndctl_cmd_vendor_get_output(struct ndctl_cmd *cmd, void *buf, unsigned int len) { ssize_t out_length = ndctl_cmd_vendor_get_output_size(cmd); if (out_length < 0) return out_length; len = min(len, out_length); memcpy(buf, to_vendor_tail(cmd)->out_buf, len); return len; } NDCTL_EXPORT struct ndctl_cmd *ndctl_dimm_cmd_new_cfg_size(struct ndctl_dimm *dimm) { struct ndctl_bus *bus = ndctl_dimm_get_bus(dimm); struct ndctl_ctx *ctx = ndctl_bus_get_ctx(bus); struct ndctl_cmd *cmd; size_t size; if (!ndctl_dimm_is_cmd_supported(dimm, ND_CMD_GET_CONFIG_SIZE)) { dbg(ctx, "unsupported cmd\n"); return NULL; } size = sizeof(*cmd) + sizeof(struct nd_cmd_get_config_size); cmd = calloc(1, size); if (!cmd) return NULL; cmd->dimm = dimm; ndctl_cmd_ref(cmd); cmd->type = ND_CMD_GET_CONFIG_SIZE; cmd->size = size; cmd->status = 1; cmd->get_firmware_status = cmd_get_firmware_status; return cmd; } NDCTL_EXPORT struct ndctl_cmd *ndctl_dimm_cmd_new_cfg_read(struct ndctl_cmd *cfg_size) { struct ndctl_ctx *ctx = ndctl_bus_get_ctx(cmd_to_bus(cfg_size)); struct ndctl_dimm *dimm = cfg_size->dimm; struct ndctl_cmd *cmd; size_t size; if (cfg_size->type != ND_CMD_GET_CONFIG_SIZE || cfg_size->status != 0) { dbg(ctx, "expected sucessfully completed cfg_size command\n"); return NULL; } if (!dimm || cfg_size->get_size->config_size == 0) { dbg(ctx, "invalid cfg_size\n"); return NULL; } if (!ndctl_dimm_is_cmd_supported(dimm, ND_CMD_GET_CONFIG_DATA)) { dbg(ctx, "unsupported cmd\n"); return NULL; } size = sizeof(*cmd) + sizeof(struct nd_cmd_get_config_data_hdr) + cfg_size->get_size->max_xfer; cmd = calloc(1, size); if (!cmd) return NULL; cmd->dimm = dimm; cmd->refcount = 1; cmd->type = ND_CMD_GET_CONFIG_DATA; cmd->size = size; cmd->status = 1; cmd->get_data->in_offset = 0; cmd->get_data->in_length = cfg_size->get_size->max_xfer; cmd->get_firmware_status = cmd_get_firmware_status; cmd->get_xfer = cmd_get_xfer; cmd->set_xfer = cmd_set_xfer; cmd->get_offset = cmd_get_offset; cmd->set_offset = cmd_set_offset; cmd->iter.init_offset = 0; cmd->iter.max_xfer = cfg_size->get_size->max_xfer; cmd->iter.data = cmd->get_data->out_buf; cmd->iter.total_xfer = cfg_size->get_size->config_size; cmd->iter.total_buf = calloc(1, cmd->iter.total_xfer); cmd->iter.dir = READ; if (!cmd->iter.total_buf) { free(cmd); return NULL; } cmd->source = cfg_size; ndctl_cmd_ref(cfg_size); return cmd; } static void iter_set_extent(struct ndctl_cmd_iter *iter, unsigned int len, unsigned int offset) { struct ndctl_cmd *cmd = container_of(iter, typeof(*cmd), iter); iter->init_offset = offset; cmd->set_offset(cmd, offset); cmd->set_xfer(cmd, min(cmd->get_xfer(cmd), len)); iter->total_xfer = len; } NDCTL_EXPORT int ndctl_cmd_cfg_read_set_extent(struct ndctl_cmd *cfg_read, unsigned int len, unsigned int offset) { struct ndctl_ctx *ctx = ndctl_bus_get_ctx(cmd_to_bus(cfg_read)); struct ndctl_cmd *cfg_size = cfg_read->source; if (cfg_read->type != ND_CMD_GET_CONFIG_DATA || cfg_read->status <= 0) { dbg(ctx, "expected unsubmitted cfg_read command\n"); return -EINVAL; } if (offset + len > cfg_size->get_size->config_size) { dbg(ctx, "read %d from %d exceeds %d\n", len, offset, cfg_size->get_size->config_size); return -EINVAL; } iter_set_extent(&cfg_read->iter, len, offset); return 0; } NDCTL_EXPORT struct ndctl_cmd *ndctl_dimm_cmd_new_cfg_write(struct ndctl_cmd *cfg_read) { struct ndctl_ctx *ctx = ndctl_bus_get_ctx(cmd_to_bus(cfg_read)); struct ndctl_dimm *dimm = cfg_read->dimm; struct ndctl_cmd *cmd; size_t size; /* enforce rmw */ if (cfg_read->type != ND_CMD_GET_CONFIG_DATA || cfg_read->status != 0) { dbg(ctx, "expected sucessfully completed cfg_read command\n"); return NULL; } if (!dimm || cfg_read->get_data->in_length == 0) { dbg(ctx, "invalid cfg_read\n"); return NULL; } if (!ndctl_dimm_is_cmd_supported(dimm, ND_CMD_SET_CONFIG_DATA)) { dbg(ctx, "unsupported cmd\n"); return NULL; } size = sizeof(*cmd) + sizeof(struct nd_cmd_set_config_hdr) + cfg_read->iter.max_xfer + 4; cmd = calloc(1, size); if (!cmd) return NULL; cmd->dimm = dimm; ndctl_cmd_ref(cmd); cmd->type = ND_CMD_SET_CONFIG_DATA; cmd->size = size; cmd->status = 1; cmd->set_data->in_offset = cfg_read->iter.init_offset; cmd->set_data->in_length = cfg_read->iter.max_xfer; cmd->get_firmware_status = cmd_get_firmware_status; cmd->get_xfer = cmd_get_xfer; cmd->set_xfer = cmd_set_xfer; cmd->get_offset = cmd_get_offset; cmd->set_offset = cmd_set_offset; cmd->iter.init_offset = cfg_read->iter.init_offset; cmd->iter.max_xfer = cfg_read->iter.max_xfer; cmd->iter.data = cmd->set_data->in_buf; cmd->iter.total_xfer = cfg_read->iter.total_xfer; cmd->iter.total_buf = cfg_read->iter.total_buf; cmd->iter.dir = WRITE; cmd->source = cfg_read; ndctl_cmd_ref(cfg_read); return cmd; } NDCTL_EXPORT unsigned int ndctl_cmd_cfg_size_get_size(struct ndctl_cmd *cfg_size) { if (cfg_size->type == ND_CMD_GET_CONFIG_SIZE && cfg_size->status == 0) return cfg_size->get_size->config_size; return 0; } static ssize_t iter_access(struct ndctl_cmd_iter *iter, unsigned int len, unsigned int offset) { if (offset < iter->init_offset || offset > iter->init_offset + iter->total_xfer || len + offset < len) return -EINVAL; if (len + offset > iter->init_offset + iter->total_xfer) len = iter->total_xfer - offset; return len; } NDCTL_EXPORT ssize_t ndctl_cmd_cfg_read_get_data(struct ndctl_cmd *cfg_read, void *buf, unsigned int _len, unsigned int offset) { struct ndctl_cmd_iter *iter; ssize_t len; if (cfg_read->type != ND_CMD_GET_CONFIG_DATA || cfg_read->status > 0) return -EINVAL; if (cfg_read->status < 0) return cfg_read->status; iter = &cfg_read->iter; len = iter_access(&cfg_read->iter, _len, offset); if (len >= 0) memcpy(buf, iter->total_buf + offset, len); return len; } NDCTL_EXPORT ssize_t ndctl_cmd_cfg_read_get_size(struct ndctl_cmd *cfg_read) { if (cfg_read->type != ND_CMD_GET_CONFIG_DATA || cfg_read->status > 0) return -EINVAL; if (cfg_read->status < 0) return cfg_read->status; return cfg_read->iter.total_xfer; } NDCTL_EXPORT int ndctl_cmd_cfg_write_set_extent(struct ndctl_cmd *cfg_write, unsigned int len, unsigned int offset) { struct ndctl_ctx *ctx = ndctl_bus_get_ctx(cmd_to_bus(cfg_write)); struct ndctl_cmd *cfg_size, *cfg_read; if (cfg_write->type != ND_CMD_SET_CONFIG_DATA || cfg_write->status <= 0) { dbg(ctx, "expected unsubmitted cfg_write command\n"); return -EINVAL; } cfg_read = cfg_write->source; cfg_size = cfg_read->source; if (offset + len > cfg_size->get_size->config_size) { dbg(ctx, "write %d from %d exceeds %d\n", len, offset, cfg_size->get_size->config_size); return -EINVAL; } iter_set_extent(&cfg_write->iter, len, offset); return 0; } NDCTL_EXPORT ssize_t ndctl_cmd_cfg_write_set_data(struct ndctl_cmd *cfg_write, void *buf, unsigned int _len, unsigned int offset) { ssize_t len; if (cfg_write->type != ND_CMD_SET_CONFIG_DATA || cfg_write->status < 1) return -EINVAL; if (cfg_write->status < 0) return cfg_write->status; len = iter_access(&cfg_write->iter, _len, offset); if (len >= 0) memcpy(cfg_write->iter.total_buf + offset, buf, len); return len; } NDCTL_EXPORT ssize_t ndctl_cmd_cfg_write_zero_data(struct ndctl_cmd *cfg_write) { struct ndctl_cmd_iter *iter = &cfg_write->iter; if (cfg_write->type != ND_CMD_SET_CONFIG_DATA || cfg_write->status < 1) return -EINVAL; if (cfg_write->status < 0) return cfg_write->status; memset(iter->total_buf + iter->init_offset, 0, iter->total_xfer); return iter->total_xfer; } NDCTL_EXPORT void ndctl_cmd_unref(struct ndctl_cmd *cmd) { if (!cmd) return; if (--cmd->refcount == 0) { if (cmd->source) ndctl_cmd_unref(cmd->source); else free(cmd->iter.total_buf); free(cmd); } } NDCTL_EXPORT void ndctl_cmd_ref(struct ndctl_cmd *cmd) { cmd->refcount++; } NDCTL_EXPORT int ndctl_cmd_get_type(struct ndctl_cmd *cmd) { return cmd->type; } static int to_ioctl_cmd(int cmd, int dimm) { if (!dimm) { switch (cmd) { case ND_CMD_ARS_CAP: return ND_IOCTL_ARS_CAP; case ND_CMD_ARS_START: return ND_IOCTL_ARS_START; case ND_CMD_ARS_STATUS: return ND_IOCTL_ARS_STATUS; case ND_CMD_CLEAR_ERROR: return ND_IOCTL_CLEAR_ERROR; case ND_CMD_CALL: return ND_IOCTL_CALL; default: return 0; }; } switch (cmd) { case ND_CMD_DIMM_FLAGS: return ND_IOCTL_DIMM_FLAGS; case ND_CMD_GET_CONFIG_SIZE: return ND_IOCTL_GET_CONFIG_SIZE; case ND_CMD_GET_CONFIG_DATA: return ND_IOCTL_GET_CONFIG_DATA; case ND_CMD_SET_CONFIG_DATA: return ND_IOCTL_SET_CONFIG_DATA; case ND_CMD_VENDOR: return ND_IOCTL_VENDOR; case ND_CMD_CALL: return ND_IOCTL_CALL; case ND_CMD_VENDOR_EFFECT_LOG_SIZE: case ND_CMD_VENDOR_EFFECT_LOG: default: return 0; } } static const char *ndctl_dimm_get_cmd_subname(struct ndctl_cmd *cmd) { struct ndctl_dimm *dimm = cmd->dimm; struct ndctl_dimm_ops *ops = dimm ? dimm->ops : NULL; if (!dimm || cmd->type != ND_CMD_CALL || !ops || !ops->cmd_desc) return NULL; return ops->cmd_desc(cmd->pkg->nd_command); } NDCTL_EXPORT int ndctl_cmd_xlat_firmware_status(struct ndctl_cmd *cmd) { struct ndctl_dimm *dimm = cmd->dimm; struct ndctl_dimm_ops *ops = dimm ? dimm->ops : NULL; if (!dimm || !ops || !ops->xlat_firmware_status) return 0; return ops->xlat_firmware_status(cmd); } static int do_cmd(int fd, int ioctl_cmd, struct ndctl_cmd *cmd) { int rc; u32 offset; const char *name, *sub_name = NULL; struct ndctl_dimm *dimm = cmd->dimm; struct ndctl_bus *bus = cmd_to_bus(cmd); struct ndctl_cmd_iter *iter = &cmd->iter; struct ndctl_ctx *ctx = ndctl_bus_get_ctx(bus); if (dimm) { name = ndctl_dimm_get_cmd_name(dimm, cmd->type); sub_name = ndctl_dimm_get_cmd_subname(cmd); } else name = ndctl_bus_get_cmd_name(cmd->bus, cmd->type); if (iter->total_xfer == 0) { rc = ioctl(fd, ioctl_cmd, cmd->cmd_buf); dbg(ctx, "bus: %d dimm: %#x cmd: %s%s%s status: %d fw: %d (%s)\n", bus->id, dimm ? ndctl_dimm_get_handle(dimm) : 0, name, sub_name ? ":" : "", sub_name ? sub_name : "", rc, cmd->get_firmware_status(cmd), rc < 0 ? strerror(errno) : "success"); if (rc < 0) return -errno; else return rc; } for (offset = 0; offset < iter->total_xfer; offset += iter->max_xfer) { cmd->set_xfer(cmd, min(iter->total_xfer - offset, iter->max_xfer)); cmd->set_offset(cmd, offset); if (iter->dir == WRITE) memcpy(iter->data, iter->total_buf + offset, cmd->get_xfer(cmd)); rc = ioctl(fd, ioctl_cmd, cmd->cmd_buf); if (rc < 0) { rc = -errno; break; } if (iter->dir == READ) memcpy(iter->total_buf + offset, iter->data, cmd->get_xfer(cmd) - rc); if (cmd->get_firmware_status(cmd) || rc) { rc = offset + cmd->get_xfer(cmd) - rc; break; } } dbg(ctx, "bus: %d dimm: %#x cmd: %s%s%s total: %d max_xfer: %d status: %d fw: %d (%s)\n", bus->id, dimm ? ndctl_dimm_get_handle(dimm) : 0, name, sub_name ? ":" : "", sub_name ? sub_name : "", iter->total_xfer, iter->max_xfer, rc, cmd->get_firmware_status(cmd), rc < 0 ? strerror(errno) : "success"); return rc; } NDCTL_EXPORT int ndctl_cmd_submit(struct ndctl_cmd *cmd) { struct stat st; char path[20], *prefix; unsigned int major, minor, id; int rc = 0, fd, len = sizeof(path); int ioctl_cmd = to_ioctl_cmd(cmd->type, !!cmd->dimm); struct ndctl_bus *bus = cmd_to_bus(cmd); struct ndctl_ctx *ctx = ndctl_bus_get_ctx(bus); if (!cmd->get_firmware_status) { err(ctx, "missing status retrieval\n"); return -EINVAL; } if (ioctl_cmd == 0) { rc = -EINVAL; goto out; } if (cmd->dimm) { prefix = "nmem"; id = ndctl_dimm_get_id(cmd->dimm); major = ndctl_dimm_get_major(cmd->dimm); minor = ndctl_dimm_get_minor(cmd->dimm); } else { prefix = "ndctl"; id = ndctl_bus_get_id(cmd->bus); major = ndctl_bus_get_major(cmd->bus); minor = ndctl_bus_get_minor(cmd->bus); } if (snprintf(path, len, "/dev/%s%u", prefix, id) >= len) { rc = -EINVAL; goto out; } fd = open(path, O_RDWR); if (fd < 0) { err(ctx, "failed to open %s: %s\n", path, strerror(errno)); rc = -errno; goto out; } if (fstat(fd, &st) >= 0 && S_ISCHR(st.st_mode) && major(st.st_rdev) == major && minor(st.st_rdev) == minor) { rc = do_cmd(fd, ioctl_cmd, cmd); } else { err(ctx, "failed to validate %s as a control node\n", path); rc = -ENXIO; } close(fd); out: cmd->status = rc; return rc; } NDCTL_EXPORT int ndctl_cmd_submit_xlat(struct ndctl_cmd *cmd) { int rc, xlat_rc; rc = ndctl_cmd_submit(cmd); if (rc < 0) return rc; /* * NOTE: This can lose a positive rc when xlat_rc is non-zero. The * positive rc indicates a buffer underrun from the original command * submission. If the caller cares about that (generally not very * useful), then the xlat function is available separately as well. */ xlat_rc = ndctl_cmd_xlat_firmware_status(cmd); return (xlat_rc == 0) ? rc : xlat_rc; } NDCTL_EXPORT int ndctl_cmd_get_status(struct ndctl_cmd *cmd) { return cmd->status; } NDCTL_EXPORT unsigned int ndctl_cmd_get_firmware_status(struct ndctl_cmd *cmd) { return cmd->get_firmware_status(cmd); } NDCTL_EXPORT const char *ndctl_region_get_devname(struct ndctl_region *region) { return devpath_to_devname(region->region_path); } static int is_enabled(struct ndctl_bus *bus, const char *drvpath) { struct stat st; ndctl_bus_wait_probe(bus); if (lstat(drvpath, &st) < 0 || !S_ISLNK(st.st_mode)) return 0; else return 1; } NDCTL_EXPORT int ndctl_region_is_enabled(struct ndctl_region *region) { struct ndctl_ctx *ctx = ndctl_region_get_ctx(region); char *path = region->region_buf; int len = region->buf_len; if (snprintf(path, len, "%s/driver", region->region_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_region_get_devname(region)); return 0; } return is_enabled(ndctl_region_get_bus(region), path); } NDCTL_EXPORT int ndctl_region_enable(struct ndctl_region *region) { struct ndctl_ctx *ctx = ndctl_region_get_ctx(region); const char *devname = ndctl_region_get_devname(region); if (ndctl_region_is_enabled(region)) return 0; util_bind(devname, region->module, "nd", ctx); if (!ndctl_region_is_enabled(region)) { err(ctx, "%s: failed to enable\n", devname); return -ENXIO; } if (region->refresh_type) { region->refresh_type = 0; region_set_type(region, region->region_buf); } dbg(ctx, "%s: enabled\n", devname); return 0; } void region_flag_refresh(struct ndctl_region *region) { region->refresh_type = 1; } NDCTL_EXPORT void ndctl_region_cleanup(struct ndctl_region *region) { free_stale_namespaces(region); free_stale_btts(region); free_stale_pfns(region); free_stale_daxs(region); } static int ndctl_region_disable(struct ndctl_region *region, int cleanup) { struct ndctl_ctx *ctx = ndctl_region_get_ctx(region); const char *devname = ndctl_region_get_devname(region); if (!ndctl_region_is_enabled(region)) return 0; util_unbind(region->region_path, ctx); if (ndctl_region_is_enabled(region)) { err(ctx, "%s: failed to disable\n", devname); return -EBUSY; } region->namespaces_init = 0; region->btts_init = 0; region->pfns_init = 0; region->daxs_init = 0; list_append_list(®ion->stale_namespaces, ®ion->namespaces); list_append_list(®ion->stale_btts, ®ion->btts); list_append_list(®ion->stale_pfns, ®ion->pfns); list_append_list(®ion->stale_daxs, ®ion->daxs); region->generation++; if (cleanup) ndctl_region_cleanup(region); dbg(ctx, "%s: disabled\n", devname); return 0; } NDCTL_EXPORT int ndctl_region_disable_invalidate(struct ndctl_region *region) { return ndctl_region_disable(region, 1); } NDCTL_EXPORT int ndctl_region_disable_preserve(struct ndctl_region *region) { return ndctl_region_disable(region, 0); } NDCTL_EXPORT struct ndctl_interleave_set *ndctl_region_get_interleave_set( struct ndctl_region *region) { unsigned int nstype = ndctl_region_get_nstype(region); if (nstype == ND_DEVICE_NAMESPACE_PMEM) return ®ion->iset; return NULL; } NDCTL_EXPORT struct ndctl_region *ndctl_interleave_set_get_region( struct ndctl_interleave_set *iset) { return container_of(iset, struct ndctl_region, iset); } NDCTL_EXPORT struct ndctl_interleave_set *ndctl_interleave_set_get_first( struct ndctl_bus *bus) { struct ndctl_region *region; ndctl_region_foreach(bus, region) { struct ndctl_interleave_set *iset; iset = ndctl_region_get_interleave_set(region); if (iset) return iset; } return NULL; } NDCTL_EXPORT struct ndctl_interleave_set *ndctl_interleave_set_get_next( struct ndctl_interleave_set *iset) { struct ndctl_region *region = ndctl_interleave_set_get_region(iset); iset = NULL; do { region = ndctl_region_get_next(region); if (!region) break; iset = ndctl_region_get_interleave_set(region); if (iset) break; } while (1); return iset; } NDCTL_EXPORT int ndctl_dimm_is_enabled(struct ndctl_dimm *dimm) { struct ndctl_ctx *ctx = ndctl_dimm_get_ctx(dimm); char *path = dimm->dimm_buf; int len = dimm->buf_len; if (snprintf(path, len, "%s/driver", dimm->dimm_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_dimm_get_devname(dimm)); return 0; } return is_enabled(ndctl_dimm_get_bus(dimm), path); } NDCTL_EXPORT int ndctl_dimm_is_active(struct ndctl_dimm *dimm) { struct ndctl_ctx *ctx = ndctl_dimm_get_ctx(dimm); char *path = dimm->dimm_buf; char buf[SYSFS_ATTR_SIZE]; int len = dimm->buf_len; if (snprintf(path, len, "%s/state", dimm->dimm_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_dimm_get_devname(dimm)); return -ENOMEM; } if (sysfs_read_attr(ctx, path, buf) < 0) return -ENXIO; if (strcmp(buf, "active") == 0) return 1; return 0; } NDCTL_EXPORT int ndctl_interleave_set_is_active( struct ndctl_interleave_set *iset) { struct ndctl_dimm *dimm; ndctl_dimm_foreach_in_interleave_set(iset, dimm) { int active = ndctl_dimm_is_active(dimm); if (active) return active; } return 0; } NDCTL_EXPORT unsigned long long ndctl_interleave_set_get_cookie( struct ndctl_interleave_set *iset) { return iset->cookie; } NDCTL_EXPORT struct ndctl_dimm *ndctl_interleave_set_get_first_dimm( struct ndctl_interleave_set *iset) { struct ndctl_region *region = ndctl_interleave_set_get_region(iset); return ndctl_region_get_first_dimm(region); } NDCTL_EXPORT struct ndctl_dimm *ndctl_interleave_set_get_next_dimm( struct ndctl_interleave_set *iset, struct ndctl_dimm *dimm) { struct ndctl_region *region = ndctl_interleave_set_get_region(iset); return ndctl_region_get_next_dimm(region, dimm); } static void mappings_init(struct ndctl_region *region) { char *mapping_path, buf[SYSFS_ATTR_SIZE]; struct ndctl_bus *bus = region->bus; struct ndctl_ctx *ctx = bus->ctx; int i; if (region->mappings_init) return; region->mappings_init = 1; mapping_path = calloc(1, strlen(region->region_path) + 100); if (!mapping_path) { err(ctx, "bus%d region%d: allocation failure\n", bus->id, region->id); return; } for (i = 0; i < region->num_mappings; i++) { struct ndctl_mapping *mapping; unsigned long long offset, length; struct ndctl_dimm *dimm; unsigned int dimm_id; int position, match; sprintf(mapping_path, "%s/mapping%d", region->region_path, i); if (sysfs_read_attr(ctx, mapping_path, buf) < 0) { err(ctx, "bus%d region%d: failed to read mapping%d\n", bus->id, region->id, i); continue; } match = sscanf(buf, "nmem%u,%llu,%llu,%d", &dimm_id, &offset, &length, &position); if (match < 4) position = -1; if (match < 3) { err(ctx, "bus%d mapping parse failure\n", ndctl_bus_get_id(bus)); continue; } dimm = ndctl_dimm_get_by_id(bus, dimm_id); if (!dimm) { err(ctx, "bus%d region%d mapping%d: nmem%d lookup failure\n", bus->id, region->id, i, dimm_id); continue; } mapping = calloc(1, sizeof(*mapping)); if (!mapping) { err(ctx, "bus%d region%d mapping%d: allocation failure\n", bus->id, region->id, i); continue; } mapping->region = region; mapping->offset = offset; mapping->length = length; mapping->dimm = dimm; mapping->position = position; list_add(®ion->mappings, &mapping->list); } free(mapping_path); } NDCTL_EXPORT struct ndctl_mapping *ndctl_mapping_get_first(struct ndctl_region *region) { mappings_init(region); return list_top(®ion->mappings, struct ndctl_mapping, list); } NDCTL_EXPORT struct ndctl_mapping *ndctl_mapping_get_next(struct ndctl_mapping *mapping) { struct ndctl_region *region = mapping->region; return list_next(®ion->mappings, mapping, list); } NDCTL_EXPORT struct ndctl_dimm *ndctl_mapping_get_dimm(struct ndctl_mapping *mapping) { return mapping->dimm; } NDCTL_EXPORT unsigned long long ndctl_mapping_get_offset(struct ndctl_mapping *mapping) { return mapping->offset; } NDCTL_EXPORT unsigned long long ndctl_mapping_get_length(struct ndctl_mapping *mapping) { return mapping->length; } NDCTL_EXPORT int ndctl_mapping_get_position(struct ndctl_mapping *mapping) { return mapping->position; } NDCTL_EXPORT struct ndctl_region *ndctl_mapping_get_region( struct ndctl_mapping *mapping) { return mapping->region; } NDCTL_EXPORT struct ndctl_bus *ndctl_mapping_get_bus( struct ndctl_mapping *mapping) { return ndctl_mapping_get_region(mapping)->bus; } NDCTL_EXPORT struct ndctl_ctx *ndctl_mapping_get_ctx( struct ndctl_mapping *mapping) { return ndctl_mapping_get_bus(mapping)->ctx; } static char *get_block_device(struct ndctl_ctx *ctx, const char *block_path) { char *bdev_name = NULL; struct dirent *de; DIR *dir; dir = opendir(block_path); if (!dir) { dbg(ctx, "no block device found: %s\n", block_path); return NULL; } while ((de = readdir(dir)) != NULL) { if (de->d_ino == 0 || de->d_name[0] == '.') continue; if (bdev_name) { dbg(ctx, "invalid block_path format: %s\n", block_path); free(bdev_name); bdev_name = NULL; break; } bdev_name = strdup(de->d_name); } closedir(dir); return bdev_name; } static int parse_lbasize_supported(struct ndctl_ctx *ctx, const char *devname, const char *buf, struct ndctl_lbasize *lba); static const char *enforce_id_to_name(enum ndctl_namespace_mode mode) { static const char *id_to_name[] = { [NDCTL_NS_MODE_MEMORY] = "pfn", [NDCTL_NS_MODE_SECTOR] = "btt", /* TODO: convert to btt2 */ [NDCTL_NS_MODE_RAW] = "", [NDCTL_NS_MODE_DAX] = "dax", [NDCTL_NS_MODE_UNKNOWN] = "", }; if (mode < NDCTL_NS_MODE_UNKNOWN && mode >= 0) return id_to_name[mode]; return id_to_name[NDCTL_NS_MODE_UNKNOWN]; } static enum ndctl_namespace_mode enforce_name_to_id(const char *name) { int i; for (i = 0; i < NDCTL_NS_MODE_UNKNOWN; i++) if (strcmp(enforce_id_to_name(i), name) == 0) return i; return NDCTL_NS_MODE_UNKNOWN; } static void *add_namespace(void *parent, int id, const char *ndns_base) { const char *devname = devpath_to_devname(ndns_base); char *path = calloc(1, strlen(ndns_base) + 100); struct ndctl_namespace *ndns, *ndns_dup; struct ndctl_region *region = parent; struct ndctl_bus *bus = region->bus; struct ndctl_ctx *ctx = bus->ctx; char buf[SYSFS_ATTR_SIZE]; if (!path) return NULL; ndns = calloc(1, sizeof(*ndns)); if (!ndns) goto err_namespace; ndns->id = id; ndns->region = region; ndns->generation = region->generation; list_head_init(&ndns->injected_bb); sprintf(path, "%s/nstype", ndns_base); if (sysfs_read_attr(ctx, path, buf) < 0) goto err_read; ndns->type = strtoul(buf, NULL, 0); sprintf(path, "%s/size", ndns_base); if (sysfs_read_attr(ctx, path, buf) < 0) goto err_read; ndns->size = strtoull(buf, NULL, 0); sprintf(path, "%s/resource", ndns_base); if (sysfs_read_attr(ctx, path, buf) < 0) ndns->resource = ULLONG_MAX; else ndns->resource = strtoull(buf, NULL, 0); sprintf(path, "%s/force_raw", ndns_base); if (sysfs_read_attr(ctx, path, buf) < 0) goto err_read; ndns->raw_mode = strtoul(buf, NULL, 0); sprintf(path, "%s/numa_node", ndns_base); if (sysfs_read_attr(ctx, path, buf) == 0) ndns->numa_node = strtol(buf, NULL, 0); else ndns->numa_node = -1; sprintf(path, "%s/target_node", ndns_base); if (sysfs_read_attr(ctx, path, buf) == 0) ndns->target_node = strtol(buf, NULL, 0); else ndns->target_node = -1; sprintf(path, "%s/holder_class", ndns_base); if (sysfs_read_attr(ctx, path, buf) == 0) ndns->enforce_mode = enforce_name_to_id(buf); switch (ndns->type) { case ND_DEVICE_NAMESPACE_BLK: case ND_DEVICE_NAMESPACE_PMEM: sprintf(path, "%s/sector_size", ndns_base); if (sysfs_read_attr(ctx, path, buf) == 0) parse_lbasize_supported(ctx, devname, buf, &ndns->lbasize); else if (ndns->type == ND_DEVICE_NAMESPACE_BLK) { /* * sector_size support is mandatory for blk, * optional for pmem. */ goto err_read; } else parse_lbasize_supported(ctx, devname, "", &ndns->lbasize); sprintf(path, "%s/alt_name", ndns_base); if (sysfs_read_attr(ctx, path, buf) < 0) goto err_read; ndns->alt_name = strdup(buf); if (!ndns->alt_name) goto err_read; sprintf(path, "%s/uuid", ndns_base); if (sysfs_read_attr(ctx, path, buf) < 0) goto err_read; if (strlen(buf) && uuid_parse(buf, ndns->uuid) < 0) { dbg(ctx, "%s:%s\n", path, buf); goto err_read; } break; default: break; } ndns->ndns_path = strdup(ndns_base); if (!ndns->ndns_path) goto err_read; ndns->ndns_buf = calloc(1, strlen(ndns_base) + 50); if (!ndns->ndns_buf) goto err_read; ndns->buf_len = strlen(ndns_base) + 50; sprintf(path, "%s/modalias", ndns_base); if (sysfs_read_attr(ctx, path, buf) < 0) goto err_read; ndns->module = util_modalias_to_module(ctx, buf); ndctl_namespace_foreach(region, ndns_dup) if (ndns_dup->id == ndns->id) { free_namespace(ndns, NULL); free(path); return ndns_dup; } list_add(®ion->namespaces, &ndns->list); free(path); return ndns; err_read: free(ndns->ndns_buf); free(ndns->ndns_path); free(ndns->alt_name); free(ndns); err_namespace: free(path); return NULL; } static void namespaces_init(struct ndctl_region *region) { struct ndctl_bus *bus = region->bus; struct ndctl_ctx *ctx = bus->ctx; char ndns_fmt[20]; if (region->namespaces_init) return; region->namespaces_init = 1; sprintf(ndns_fmt, "namespace%d.", region->id); device_parse(ctx, bus, region->region_path, ndns_fmt, region, add_namespace); } NDCTL_EXPORT struct ndctl_namespace *ndctl_namespace_get_first(struct ndctl_region *region) { namespaces_init(region); return list_top(®ion->namespaces, struct ndctl_namespace, list); } NDCTL_EXPORT struct ndctl_namespace *ndctl_namespace_get_next(struct ndctl_namespace *ndns) { struct ndctl_region *region = ndns->region; return list_next(®ion->namespaces, ndns, list); } NDCTL_EXPORT unsigned int ndctl_namespace_get_id(struct ndctl_namespace *ndns) { return ndns->id; } NDCTL_EXPORT unsigned int ndctl_namespace_get_type(struct ndctl_namespace *ndns) { return ndns->type; } NDCTL_EXPORT const char *ndctl_namespace_get_type_name(struct ndctl_namespace *ndns) { return ndctl_device_type_name(ndns->type); } NDCTL_EXPORT struct ndctl_region *ndctl_namespace_get_region(struct ndctl_namespace *ndns) { return ndns->region; } NDCTL_EXPORT struct ndctl_bus *ndctl_namespace_get_bus(struct ndctl_namespace *ndns) { return ndns->region->bus; } NDCTL_EXPORT struct ndctl_ctx *ndctl_namespace_get_ctx(struct ndctl_namespace *ndns) { return ndns->region->bus->ctx; } NDCTL_EXPORT const char *ndctl_namespace_get_devname(struct ndctl_namespace *ndns) { return devpath_to_devname(ndns->ndns_path); } NDCTL_EXPORT struct ndctl_btt *ndctl_namespace_get_btt(struct ndctl_namespace *ndns) { struct ndctl_region *region = ndctl_namespace_get_region(ndns); struct ndctl_ctx *ctx = ndctl_namespace_get_ctx(ndns); char *path = ndns->ndns_buf; int len = ndns->buf_len; struct ndctl_btt *btt; char buf[SYSFS_ATTR_SIZE]; if (snprintf(path, len, "%s/holder", ndns->ndns_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_namespace_get_devname(ndns)); return NULL; } if (sysfs_read_attr(ctx, path, buf) < 0) return NULL; ndctl_btt_foreach(region, btt) if (strcmp(buf, ndctl_btt_get_devname(btt)) == 0) return btt; return NULL; } NDCTL_EXPORT struct ndctl_pfn *ndctl_namespace_get_pfn(struct ndctl_namespace *ndns) { struct ndctl_region *region = ndctl_namespace_get_region(ndns); struct ndctl_ctx *ctx = ndctl_namespace_get_ctx(ndns); char *path = ndns->ndns_buf; int len = ndns->buf_len; struct ndctl_pfn *pfn; char buf[SYSFS_ATTR_SIZE]; if (snprintf(path, len, "%s/holder", ndns->ndns_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_namespace_get_devname(ndns)); return NULL; } if (sysfs_read_attr(ctx, path, buf) < 0) return NULL; ndctl_pfn_foreach(region, pfn) if (strcmp(buf, ndctl_pfn_get_devname(pfn)) == 0) return pfn; return NULL; } NDCTL_EXPORT struct ndctl_dax *ndctl_namespace_get_dax(struct ndctl_namespace *ndns) { struct ndctl_region *region = ndctl_namespace_get_region(ndns); struct ndctl_ctx *ctx = ndctl_namespace_get_ctx(ndns); char *path = ndns->ndns_buf; int len = ndns->buf_len; struct ndctl_dax *dax; char buf[SYSFS_ATTR_SIZE]; if (snprintf(path, len, "%s/holder", ndns->ndns_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_namespace_get_devname(ndns)); return NULL; } if (sysfs_read_attr(ctx, path, buf) < 0) return NULL; ndctl_dax_foreach(region, dax) if (strcmp(buf, ndctl_dax_get_devname(dax)) == 0) return dax; return NULL; } NDCTL_EXPORT const char *ndctl_namespace_get_block_device(struct ndctl_namespace *ndns) { struct ndctl_ctx *ctx = ndctl_namespace_get_ctx(ndns); struct ndctl_bus *bus = ndctl_namespace_get_bus(ndns); char *path = ndns->ndns_buf; int len = ndns->buf_len; if (ndns->bdev) return ndns->bdev; if (snprintf(path, len, "%s/block", ndns->ndns_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_namespace_get_devname(ndns)); return ""; } ndctl_bus_wait_probe(bus); ndns->bdev = get_block_device(ctx, path); return ndns->bdev ? ndns->bdev : ""; } NDCTL_EXPORT enum ndctl_namespace_mode ndctl_namespace_get_mode( struct ndctl_namespace *ndns) { struct ndctl_ctx *ctx = ndctl_namespace_get_ctx(ndns); char *path = ndns->ndns_buf; int len = ndns->buf_len; char buf[SYSFS_ATTR_SIZE]; if (snprintf(path, len, "%s/mode", ndns->ndns_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_namespace_get_devname(ndns)); return -ENOMEM; } if (sysfs_read_attr(ctx, path, buf) < 0) return -ENXIO; if (strcmp("memory", buf) == 0) return NDCTL_NS_MODE_MEMORY; if (strcmp("dax", buf) == 0) return NDCTL_NS_MODE_DAX; if (strcmp("raw", buf) == 0) return NDCTL_NS_MODE_RAW; if (strcmp("safe", buf) == 0) return NDCTL_NS_MODE_SECTOR; return -ENXIO; } NDCTL_EXPORT enum ndctl_namespace_mode ndctl_namespace_get_enforce_mode( struct ndctl_namespace *ndns) { return ndns->enforce_mode; } NDCTL_EXPORT int ndctl_namespace_set_enforce_mode(struct ndctl_namespace *ndns, enum ndctl_namespace_mode mode) { struct ndctl_ctx *ctx = ndctl_namespace_get_ctx(ndns); char *path = ndns->ndns_buf; int len = ndns->buf_len; int rc; if (mode < 0 || mode >= NDCTL_NS_MODE_UNKNOWN) return -EINVAL; if (snprintf(path, len, "%s/holder_class", ndns->ndns_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_namespace_get_devname(ndns)); return -ENOMEM; } rc = sysfs_write_attr(ctx, path, enforce_id_to_name(mode)); if (rc >= 0) ndns->enforce_mode = mode; return rc; } NDCTL_EXPORT int ndctl_namespace_is_valid(struct ndctl_namespace *ndns) { struct ndctl_region *region = ndctl_namespace_get_region(ndns); return ndns->generation == region->generation; } NDCTL_EXPORT int ndctl_namespace_get_raw_mode(struct ndctl_namespace *ndns) { return ndns->raw_mode; } NDCTL_EXPORT int ndctl_namespace_set_raw_mode(struct ndctl_namespace *ndns, int raw_mode) { struct ndctl_ctx *ctx = ndctl_namespace_get_ctx(ndns); char *path = ndns->ndns_buf; int len = ndns->buf_len, rc; if (snprintf(path, len, "%s/force_raw", ndns->ndns_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_namespace_get_devname(ndns)); return -ENXIO; } raw_mode = !!raw_mode; rc = sysfs_write_attr(ctx, path, raw_mode ? "1\n" : "0\n"); if (rc < 0) return rc; ndns->raw_mode = raw_mode; return raw_mode; } NDCTL_EXPORT int ndctl_namespace_is_enabled(struct ndctl_namespace *ndns) { struct ndctl_ctx *ctx = ndctl_namespace_get_ctx(ndns); char *path = ndns->ndns_buf; int len = ndns->buf_len; if (snprintf(path, len, "%s/driver", ndns->ndns_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_namespace_get_devname(ndns)); return 0; } return is_enabled(ndctl_namespace_get_bus(ndns), path); } NDCTL_EXPORT struct badblock *ndctl_namespace_get_next_badblock( struct ndctl_namespace *ndns) { return badblocks_iter_next(&ndns->bb_iter); } NDCTL_EXPORT struct badblock *ndctl_namespace_get_first_badblock( struct ndctl_namespace *ndns) { struct ndctl_btt *btt = ndctl_namespace_get_btt(ndns); struct ndctl_pfn *pfn = ndctl_namespace_get_pfn(ndns); struct ndctl_dax *dax = ndctl_namespace_get_dax(ndns); struct ndctl_ctx *ctx = ndctl_namespace_get_ctx(ndns); const char *dev = ndctl_namespace_get_devname(ndns); char path[SYSFS_ATTR_SIZE]; ssize_t len = sizeof(path); const char *bdev; if (btt || dax) { dbg(ctx, "%s: badblocks not supported for %s\n", dev, btt ? "btt" : "device-dax"); return NULL; } if (pfn) bdev = ndctl_pfn_get_block_device(pfn); else bdev = ndctl_namespace_get_block_device(ndns); if (!bdev) { dbg(ctx, "%s: failed to determine block device\n", dev); return NULL; } if (snprintf(path, len, "/sys/block/%s", bdev) >= len) { err(ctx, "%s: buffer too small!\n", dev); return NULL; } return badblocks_iter_first(&ndns->bb_iter, ctx, path); } static void *add_btt(void *parent, int id, const char *btt_base); static void *add_pfn(void *parent, int id, const char *pfn_base); static void *add_dax(void *parent, int id, const char *dax_base); static void btts_init(struct ndctl_region *region) { struct ndctl_bus *bus = ndctl_region_get_bus(region); char btt_fmt[20]; if (region->btts_init) return; region->btts_init = 1; sprintf(btt_fmt, "btt%d.", region->id); device_parse(bus->ctx, bus, region->region_path, btt_fmt, region, add_btt); } static void pfns_init(struct ndctl_region *region) { struct ndctl_bus *bus = ndctl_region_get_bus(region); char pfn_fmt[20]; if (region->pfns_init) return; region->pfns_init = 1; sprintf(pfn_fmt, "pfn%d.", region->id); device_parse(bus->ctx, bus, region->region_path, pfn_fmt, region, add_pfn); } static void daxs_init(struct ndctl_region *region) { struct ndctl_bus *bus = ndctl_region_get_bus(region); char dax_fmt[20]; if (region->daxs_init) return; region->daxs_init = 1; sprintf(dax_fmt, "dax%d.", region->id); device_parse(bus->ctx, bus, region->region_path, dax_fmt, region, add_dax); } static void region_refresh_children(struct ndctl_region *region) { region->namespaces_init = 0; region->btts_init = 0; region->pfns_init = 0; region->daxs_init = 0; namespaces_init(region); btts_init(region); pfns_init(region); daxs_init(region); } NDCTL_EXPORT bool ndctl_namespace_is_active(struct ndctl_namespace *ndns) { struct ndctl_btt *btt = ndctl_namespace_get_btt(ndns); struct ndctl_pfn *pfn = ndctl_namespace_get_pfn(ndns); struct ndctl_dax *dax = ndctl_namespace_get_dax(ndns); if ((btt && ndctl_btt_is_enabled(btt)) || (pfn && ndctl_pfn_is_enabled(pfn)) || (dax && ndctl_dax_is_enabled(dax)) || (!btt && !pfn && !dax && ndctl_namespace_is_enabled(ndns))) return true; return false; } /* * Return 0 if enabled, < 0 if failed to enable, and > 0 if claimed by * another device and that device is enabled. In the > 0 case a * subsequent call to ndctl_namespace_is_enabled() will return 'false'. */ NDCTL_EXPORT int ndctl_namespace_enable(struct ndctl_namespace *ndns) { const char *devname = ndctl_namespace_get_devname(ndns); struct ndctl_ctx *ctx = ndctl_namespace_get_ctx(ndns); struct ndctl_region *region = ndns->region; int rc; if (ndctl_namespace_is_enabled(ndns)) return 0; rc = util_bind(devname, ndns->module, "nd", ctx); /* * Rescan now as successfully enabling a namespace device leads * to a new one being created, and potentially btts, pfns, or * daxs being attached */ region_refresh_children(region); if (!ndctl_namespace_is_enabled(ndns)) { struct ndctl_btt *btt = ndctl_namespace_get_btt(ndns); struct ndctl_pfn *pfn = ndctl_namespace_get_pfn(ndns); struct ndctl_dax *dax = ndctl_namespace_get_dax(ndns); if (btt && ndctl_btt_is_enabled(btt)) { dbg(ctx, "%s: enabled via %s\n", devname, ndctl_btt_get_devname(btt)); return 1; } if (pfn && ndctl_pfn_is_enabled(pfn)) { dbg(ctx, "%s: enabled via %s\n", devname, ndctl_pfn_get_devname(pfn)); return 1; } if (dax && ndctl_dax_is_enabled(dax)) { dbg(ctx, "%s: enabled via %s\n", devname, ndctl_dax_get_devname(dax)); return 1; } err(ctx, "%s: failed to enable\n", devname); return rc ? rc : -ENXIO; } rc = 0; dbg(ctx, "%s: enabled\n", devname); return rc; } NDCTL_EXPORT int ndctl_namespace_disable(struct ndctl_namespace *ndns) { struct ndctl_ctx *ctx = ndctl_namespace_get_ctx(ndns); const char *devname = ndctl_namespace_get_devname(ndns); if (!ndctl_namespace_is_enabled(ndns)) return 0; util_unbind(ndns->ndns_path, ctx); if (ndctl_namespace_is_enabled(ndns)) { err(ctx, "%s: failed to disable\n", devname); return -EBUSY; } free(ndns->bdev); ndns->bdev = NULL; dbg(ctx, "%s: disabled\n", devname); return 0; } NDCTL_EXPORT int ndctl_namespace_disable_invalidate(struct ndctl_namespace *ndns) { struct ndctl_btt *btt = ndctl_namespace_get_btt(ndns); struct ndctl_pfn *pfn = ndctl_namespace_get_pfn(ndns); struct ndctl_dax *dax = ndctl_namespace_get_dax(ndns); int rc = 0; if (btt) rc = ndctl_btt_delete(btt); if (pfn) rc = ndctl_pfn_delete(pfn); if (dax) rc = ndctl_dax_delete(dax); if (rc) return rc; return ndctl_namespace_disable(ndns); } static int ndctl_dax_has_active_memory(struct ndctl_dax *dax) { struct daxctl_region *dax_region; struct daxctl_dev *dax_dev; dax_region = ndctl_dax_get_daxctl_region(dax); if (!dax_region) return 0; daxctl_dev_foreach(dax_region, dax_dev) if (daxctl_dev_has_online_memory(dax_dev)) return 1; return 0; } NDCTL_EXPORT int ndctl_namespace_disable_safe(struct ndctl_namespace *ndns) { const char *devname = ndctl_namespace_get_devname(ndns); struct ndctl_ctx *ctx = ndctl_namespace_get_ctx(ndns); struct ndctl_pfn *pfn = ndctl_namespace_get_pfn(ndns); struct ndctl_btt *btt = ndctl_namespace_get_btt(ndns); struct ndctl_dax *dax = ndctl_namespace_get_dax(ndns); const char *bdev = NULL; int fd, active = 0; char path[50]; if (pfn && ndctl_pfn_is_enabled(pfn)) bdev = ndctl_pfn_get_block_device(pfn); else if (btt && ndctl_btt_is_enabled(btt)) bdev = ndctl_btt_get_block_device(btt); else if (dax && ndctl_dax_is_enabled(dax)) active = ndctl_dax_has_active_memory(dax); else if (ndctl_namespace_is_enabled(ndns)) bdev = ndctl_namespace_get_block_device(ndns); if (bdev) { sprintf(path, "/dev/%s", bdev); fd = open(path, O_RDWR|O_EXCL); if (fd >= 0) { /* * Got it, now block new mounts while we have it * pinned. */ ndctl_namespace_disable_invalidate(ndns); close(fd); } else { /* * Yes, TOCTOU hole, but if you're racing namespace * creation you have other problems, and there's nothing * stopping the !bdev case from racing to mount an fs or * re-enabling the namepace. */ dbg(ctx, "%s: %s failed exclusive open: %s\n", devname, bdev, strerror(errno)); return -errno; } } else if (active) { dbg(ctx, "%s: active as system-ram, refusing to disable\n", devname); return -EBUSY; } else { ndctl_namespace_disable_invalidate(ndns); } return 0; } static int pmem_namespace_is_configured(struct ndctl_namespace *ndns) { if (ndctl_namespace_get_size(ndns) < ND_MIN_NAMESPACE_SIZE) return 0; if (memcmp(&ndns->uuid, null_uuid, sizeof(null_uuid)) == 0) return 0; return 1; } static int blk_namespace_is_configured(struct ndctl_namespace *ndns) { if (pmem_namespace_is_configured(ndns) == 0) return 0; if (ndctl_namespace_get_sector_size(ndns) == 0) return 0; return 1; } NDCTL_EXPORT int ndctl_namespace_is_configured(struct ndctl_namespace *ndns) { struct ndctl_ctx *ctx = ndctl_namespace_get_ctx(ndns); switch (ndctl_namespace_get_type(ndns)) { case ND_DEVICE_NAMESPACE_PMEM: return pmem_namespace_is_configured(ndns); case ND_DEVICE_NAMESPACE_IO: return 1; case ND_DEVICE_NAMESPACE_BLK: return blk_namespace_is_configured(ndns); default: dbg(ctx, "%s: nstype: %d is_configured() not implemented\n", ndctl_namespace_get_devname(ndns), ndctl_namespace_get_type(ndns)); return -ENXIO; } } /* * Check if a given 'seed' namespace is ok to configure. * If a size or uuid is present, it is considered not configuration-idle, * except in the case of legacy (ND_DEVICE_NAMESPACE_IO) namespaces. In * that case, the size is never zero, but the namespace can still be * reconfigured. */ NDCTL_EXPORT int ndctl_namespace_is_configuration_idle( struct ndctl_namespace *ndns) { if (ndctl_namespace_is_active(ndns)) return 0; if (ndctl_namespace_is_configured(ndns)) { if (ndctl_namespace_get_type(ndns) == ND_DEVICE_NAMESPACE_IO) return 1; return 0; } /* !active and !configured is configuration-idle */ return 1; } NDCTL_EXPORT void ndctl_namespace_get_uuid(struct ndctl_namespace *ndns, uuid_t uu) { memcpy(uu, ndns->uuid, sizeof(uuid_t)); } NDCTL_EXPORT int ndctl_namespace_set_uuid(struct ndctl_namespace *ndns, uuid_t uu) { struct ndctl_ctx *ctx = ndctl_namespace_get_ctx(ndns); char *path = ndns->ndns_buf; int len = ndns->buf_len, rc; char uuid[40]; if (snprintf(path, len, "%s/uuid", ndns->ndns_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_namespace_get_devname(ndns)); return -ENXIO; } uuid_unparse(uu, uuid); rc = sysfs_write_attr(ctx, path, uuid); if (rc != 0) return rc; memcpy(ndns->uuid, uu, sizeof(uuid_t)); return 0; } NDCTL_EXPORT unsigned int ndctl_namespace_get_supported_sector_size( struct ndctl_namespace *ndns, int i) { if (ndns->lbasize.num == 0) return 0; if (i < 0 || i > ndns->lbasize.num) { errno = EINVAL; return UINT_MAX; } else return ndns->lbasize.supported[i]; } NDCTL_EXPORT unsigned int ndctl_namespace_get_sector_size(struct ndctl_namespace *ndns) { return ndctl_namespace_get_supported_sector_size(ndns, ndns->lbasize.select); } NDCTL_EXPORT int ndctl_namespace_get_num_sector_sizes(struct ndctl_namespace *ndns) { return ndns->lbasize.num; } NDCTL_EXPORT int ndctl_namespace_set_sector_size(struct ndctl_namespace *ndns, unsigned int sector_size) { struct ndctl_ctx *ctx = ndctl_namespace_get_ctx(ndns); char *path = ndns->ndns_buf; int len = ndns->buf_len, rc; char sector_str[40]; int i; for (i = 0; i < ndns->lbasize.num; i++) if (ndns->lbasize.supported[i] == sector_size) break; if (i > ndns->lbasize.num) { err(ctx, "%s: unsupported sector size %d\n", ndctl_namespace_get_devname(ndns), sector_size); return -EOPNOTSUPP; } if (snprintf(path, len, "%s/sector_size", ndns->ndns_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_namespace_get_devname(ndns)); return -ENXIO; } sprintf(sector_str, "%d\n", sector_size); rc = sysfs_write_attr(ctx, path, sector_str); if (rc != 0) return rc; ndns->lbasize.select = i; return 0; } NDCTL_EXPORT const char *ndctl_namespace_get_alt_name(struct ndctl_namespace *ndns) { if (ndns->alt_name) return ndns->alt_name; return ""; } NDCTL_EXPORT int ndctl_namespace_set_alt_name(struct ndctl_namespace *ndns, const char *alt_name) { struct ndctl_ctx *ctx = ndctl_namespace_get_ctx(ndns); char *path = ndns->ndns_buf; int len = ndns->buf_len, rc; char *buf; if (!ndns->alt_name) return 0; if (strlen(alt_name) >= (size_t) NSLABEL_NAME_LEN) return -EINVAL; if (snprintf(path, len, "%s/alt_name", ndns->ndns_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_namespace_get_devname(ndns)); return -ENXIO; } buf = strdup(alt_name); if (!buf) return -ENOMEM; rc = sysfs_write_attr(ctx, path, buf); if (rc < 0) { free(buf); return rc; } free(ndns->alt_name); ndns->alt_name = buf; return 0; } NDCTL_EXPORT unsigned long long ndctl_namespace_get_size(struct ndctl_namespace *ndns) { return ndns->size; } NDCTL_EXPORT unsigned long long ndctl_namespace_get_resource(struct ndctl_namespace *ndns) { return ndns->resource; } static int namespace_set_size(struct ndctl_namespace *ndns, unsigned long long size) { struct ndctl_ctx *ctx = ndctl_namespace_get_ctx(ndns); char *path = ndns->ndns_buf; int len = ndns->buf_len, rc; char buf[SYSFS_ATTR_SIZE]; if (snprintf(path, len, "%s/size", ndns->ndns_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_namespace_get_devname(ndns)); return -ENXIO; } sprintf(buf, "%#llx\n", size); rc = sysfs_write_attr(ctx, path, buf); if (rc < 0) return rc; ndns->size = size; /* * A size change event invalidates / establishes 'resource', try * to refresh it. */ if (snprintf(path, len, "%s/resource", ndns->ndns_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_namespace_get_devname(ndns)); ndns->resource = ULLONG_MAX; return 0; } if (sysfs_read_attr(ctx, path, buf) < 0) { ndns->resource = ULLONG_MAX; return 0; } ndns->resource = strtoull(buf, NULL, 0); return 0; } NDCTL_EXPORT int ndctl_namespace_set_size(struct ndctl_namespace *ndns, unsigned long long size) { struct ndctl_ctx *ctx = ndctl_namespace_get_ctx(ndns); if (size == 0) { dbg(ctx, "%s: use ndctl_namespace_delete() instead\n", ndctl_namespace_get_devname(ndns)); return -EINVAL; } if (ndctl_namespace_is_enabled(ndns)) return -EBUSY; switch (ndctl_namespace_get_type(ndns)) { case ND_DEVICE_NAMESPACE_PMEM: case ND_DEVICE_NAMESPACE_BLK: return namespace_set_size(ndns, size); default: dbg(ctx, "%s: nstype: %d set size failed\n", ndctl_namespace_get_devname(ndns), ndctl_namespace_get_type(ndns)); return -ENXIO; } } NDCTL_EXPORT int ndctl_namespace_get_numa_node(struct ndctl_namespace *ndns) { return ndns->numa_node; } NDCTL_EXPORT int ndctl_namespace_get_target_node(struct ndctl_namespace *ndns) { return ndns->target_node; } static int __ndctl_namespace_set_write_cache(struct ndctl_namespace *ndns, int state) { struct ndctl_ctx *ctx = ndctl_namespace_get_ctx(ndns); struct ndctl_pfn *pfn = ndctl_namespace_get_pfn(ndns); char *path = ndns->ndns_buf; char buf[SYSFS_ATTR_SIZE]; int len = ndns->buf_len; const char *bdev; if (state != 1 && state != 0) return -ENXIO; if (pfn) bdev = ndctl_pfn_get_block_device(pfn); else bdev = ndctl_namespace_get_block_device(ndns); if (!bdev) return -ENXIO; if (snprintf(path, len, "/sys/block/%s/dax/write_cache", bdev) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_namespace_get_devname(ndns)); return -ENXIO; } sprintf(buf, "%d\n", state); return sysfs_write_attr(ctx, path, buf); } NDCTL_EXPORT int ndctl_namespace_enable_write_cache( struct ndctl_namespace *ndns) { return __ndctl_namespace_set_write_cache(ndns, 1); } NDCTL_EXPORT int ndctl_namespace_disable_write_cache( struct ndctl_namespace *ndns) { return __ndctl_namespace_set_write_cache(ndns, 0); } NDCTL_EXPORT int ndctl_namespace_write_cache_is_enabled( struct ndctl_namespace *ndns) { struct ndctl_ctx *ctx = ndctl_namespace_get_ctx(ndns); struct ndctl_pfn *pfn = ndctl_namespace_get_pfn(ndns); int len = ndns->buf_len, wc; char *path = ndns->ndns_buf; char buf[SYSFS_ATTR_SIZE]; const char *bdev; if (pfn) bdev = ndctl_pfn_get_block_device(pfn); else bdev = ndctl_namespace_get_block_device(ndns); if (!bdev) return -ENXIO; if (snprintf(path, len, "/sys/block/%s/dax/write_cache", bdev) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_namespace_get_devname(ndns)); return -ENXIO; } if (sysfs_read_attr(ctx, path, buf) < 0) return -ENXIO; if (sscanf(buf, "%d", &wc) == 1) if (wc) return 1; return 0; } NDCTL_EXPORT int ndctl_namespace_delete(struct ndctl_namespace *ndns) { struct ndctl_region *region = ndctl_namespace_get_region(ndns); struct ndctl_ctx *ctx = ndctl_namespace_get_ctx(ndns); int rc; if (!ndctl_namespace_is_valid(ndns)) { free_namespace(ndns, ®ion->stale_namespaces); return 0; } if (ndctl_namespace_is_enabled(ndns)) return -EBUSY; switch (ndctl_namespace_get_type(ndns)) { case ND_DEVICE_NAMESPACE_PMEM: case ND_DEVICE_NAMESPACE_BLK: break; default: dbg(ctx, "%s: nstype: %d not deletable\n", ndctl_namespace_get_devname(ndns), ndctl_namespace_get_type(ndns)); return 0; } rc = namespace_set_size(ndns, 0); /* * if the namespace has already been deleted, this will return * -ENXIO due to the uuid check in __size_store. We can safely * ignore it in the case of writing a zero. */ if (rc && (rc != -ENXIO)) return rc; region->namespaces_init = 0; free_namespace(ndns, ®ion->namespaces); return 0; } static int parse_lbasize_supported(struct ndctl_ctx *ctx, const char *devname, const char *buf, struct ndctl_lbasize *lba) { char *s = strdup(buf), *end, *field; void *temp; if (!s) return -ENOMEM; field = s; lba->num = 0; end = strchr(s, ' '); lba->select = -1; lba->supported = NULL; while (end) { unsigned int val; *end = '\0'; if (sscanf(field, "[%d]", &val) == 1) { if (lba->select >= 0) goto err; lba->select = lba->num; } else if (sscanf(field, "%d", &val) == 1) { /* pass */; } else { break; } temp = realloc(lba->supported, sizeof(unsigned int) * ++lba->num); if (temp != NULL) lba->supported = temp; else goto err; lba->supported[lba->num - 1] = val; field = end + 1; end = strchr(field, ' '); } free(s); dbg(ctx, "%s: %s\n", devname, buf); return 0; err: free(s); free(lba->supported); lba->supported = NULL; lba->select = -1; return -ENXIO; } static void *add_btt(void *parent, int id, const char *btt_base) { struct ndctl_ctx *ctx = ndctl_region_get_ctx(parent); const char *devname = devpath_to_devname(btt_base); char *path = calloc(1, strlen(btt_base) + 100); struct ndctl_region *region = parent; struct ndctl_btt *btt, *btt_dup; char buf[SYSFS_ATTR_SIZE]; if (!path) return NULL; btt = calloc(1, sizeof(*btt)); if (!btt) goto err_btt; btt->id = id; btt->region = region; btt->generation = region->generation; btt->btt_path = strdup(btt_base); if (!btt->btt_path) goto err_read; btt->btt_buf = calloc(1, strlen(btt_base) + 50); if (!btt->btt_buf) goto err_read; btt->buf_len = strlen(btt_base) + 50; sprintf(path, "%s/modalias", btt_base); if (sysfs_read_attr(ctx, path, buf) < 0) goto err_read; btt->module = util_modalias_to_module(ctx, buf); sprintf(path, "%s/uuid", btt_base); if (sysfs_read_attr(ctx, path, buf) < 0) goto err_read; if (strlen(buf) && uuid_parse(buf, btt->uuid) < 0) goto err_read; sprintf(path, "%s/sector_size", btt_base); if (sysfs_read_attr(ctx, path, buf) < 0) goto err_read; if (parse_lbasize_supported(ctx, devname, buf, &btt->lbasize) < 0) goto err_read; sprintf(path, "%s/size", btt_base); if (sysfs_read_attr(ctx, path, buf) < 0) btt->size = ULLONG_MAX; else btt->size = strtoull(buf, NULL, 0); free(path); ndctl_btt_foreach(region, btt_dup) if (btt->id == btt_dup->id) { btt_dup->size = btt->size; free_btt(btt, NULL); return btt_dup; } list_add(®ion->btts, &btt->list); return btt; err_read: free(btt->lbasize.supported); free(btt->btt_buf); free(btt->btt_path); free(btt); err_btt: free(path); return NULL; } NDCTL_EXPORT struct ndctl_btt *ndctl_btt_get_first(struct ndctl_region *region) { btts_init(region); return list_top(®ion->btts, struct ndctl_btt, list); } NDCTL_EXPORT struct ndctl_btt *ndctl_btt_get_next(struct ndctl_btt *btt) { struct ndctl_region *region = btt->region; return list_next(®ion->btts, btt, list); } NDCTL_EXPORT unsigned int ndctl_btt_get_id(struct ndctl_btt *btt) { return btt->id; } NDCTL_EXPORT unsigned int ndctl_btt_get_supported_sector_size( struct ndctl_btt *btt, int i) { if (i < 0 || i > btt->lbasize.num) { errno = EINVAL; return UINT_MAX; } else return btt->lbasize.supported[i]; } NDCTL_EXPORT unsigned int ndctl_btt_get_sector_size(struct ndctl_btt *btt) { return ndctl_btt_get_supported_sector_size(btt, btt->lbasize.select); } NDCTL_EXPORT int ndctl_btt_get_num_sector_sizes(struct ndctl_btt *btt) { return btt->lbasize.num; } NDCTL_EXPORT struct ndctl_namespace *ndctl_btt_get_namespace(struct ndctl_btt *btt) { struct ndctl_ctx *ctx = ndctl_btt_get_ctx(btt); struct ndctl_namespace *ndns, *found = NULL; struct ndctl_region *region = btt->region; char *path = region->region_buf; int len = region->buf_len; char buf[SYSFS_ATTR_SIZE]; if (btt->ndns) return btt->ndns; if (snprintf(path, len, "%s/namespace", btt->btt_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_btt_get_devname(btt)); return NULL; } if (sysfs_read_attr(ctx, path, buf) < 0) return NULL; ndctl_namespace_foreach(region, ndns) if (strcmp(buf, ndctl_namespace_get_devname(ndns)) == 0) found = ndns; btt->ndns = found; return found; } NDCTL_EXPORT void ndctl_btt_get_uuid(struct ndctl_btt *btt, uuid_t uu) { memcpy(uu, btt->uuid, sizeof(uuid_t)); } NDCTL_EXPORT unsigned long long ndctl_btt_get_size(struct ndctl_btt *btt) { return btt->size; } NDCTL_EXPORT int ndctl_btt_set_uuid(struct ndctl_btt *btt, uuid_t uu) { struct ndctl_ctx *ctx = ndctl_btt_get_ctx(btt); char *path = btt->btt_buf; int len = btt->buf_len, rc; char uuid[40]; if (snprintf(path, len, "%s/uuid", btt->btt_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_btt_get_devname(btt)); return -ENXIO; } uuid_unparse(uu, uuid); rc = sysfs_write_attr(ctx, path, uuid); if (rc != 0) return rc; memcpy(btt->uuid, uu, sizeof(uuid_t)); return 0; } NDCTL_EXPORT int ndctl_btt_set_sector_size(struct ndctl_btt *btt, unsigned int sector_size) { struct ndctl_ctx *ctx = ndctl_btt_get_ctx(btt); char *path = btt->btt_buf; int len = btt->buf_len, rc; char sector_str[40]; int i; if (snprintf(path, len, "%s/sector_size", btt->btt_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_btt_get_devname(btt)); return -ENXIO; } sprintf(sector_str, "%d\n", sector_size); rc = sysfs_write_attr(ctx, path, sector_str); if (rc != 0) return rc; for (i = 0; i < btt->lbasize.num; i++) if (btt->lbasize.supported[i] == sector_size) btt->lbasize.select = i; return 0; } NDCTL_EXPORT int ndctl_btt_set_namespace(struct ndctl_btt *btt, struct ndctl_namespace *ndns) { struct ndctl_ctx *ctx = ndctl_btt_get_ctx(btt); int len = btt->buf_len, rc; char *path = btt->btt_buf; if (snprintf(path, len, "%s/namespace", btt->btt_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_btt_get_devname(btt)); return -ENXIO; } rc = sysfs_write_attr(ctx, path, ndns ? ndctl_namespace_get_devname(ndns) : "\n"); if (rc != 0) return rc; btt->ndns = ndns; return 0; } NDCTL_EXPORT struct ndctl_bus *ndctl_btt_get_bus(struct ndctl_btt *btt) { return btt->region->bus; } NDCTL_EXPORT struct ndctl_ctx *ndctl_btt_get_ctx(struct ndctl_btt *btt) { return ndctl_bus_get_ctx(ndctl_btt_get_bus(btt)); } NDCTL_EXPORT const char *ndctl_btt_get_devname(struct ndctl_btt *btt) { return devpath_to_devname(btt->btt_path); } NDCTL_EXPORT const char *ndctl_btt_get_block_device(struct ndctl_btt *btt) { struct ndctl_ctx *ctx = ndctl_btt_get_ctx(btt); struct ndctl_bus *bus = ndctl_btt_get_bus(btt); char *path = btt->btt_buf; int len = btt->buf_len; if (btt->bdev) return btt->bdev; if (snprintf(path, len, "%s/block", btt->btt_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_btt_get_devname(btt)); return ""; } ndctl_bus_wait_probe(bus); btt->bdev = get_block_device(ctx, path); return btt->bdev ? btt->bdev : ""; } NDCTL_EXPORT int ndctl_btt_is_valid(struct ndctl_btt *btt) { struct ndctl_region *region = ndctl_btt_get_region(btt); return btt->generation == region->generation; } NDCTL_EXPORT int ndctl_btt_is_enabled(struct ndctl_btt *btt) { struct ndctl_ctx *ctx = ndctl_btt_get_ctx(btt); char *path = btt->btt_buf; int len = btt->buf_len; if (snprintf(path, len, "%s/driver", btt->btt_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_btt_get_devname(btt)); return 0; } return is_enabled(ndctl_btt_get_bus(btt), path); } NDCTL_EXPORT struct ndctl_region *ndctl_btt_get_region(struct ndctl_btt *btt) { return btt->region; } NDCTL_EXPORT int ndctl_btt_enable(struct ndctl_btt *btt) { struct ndctl_region *region = ndctl_btt_get_region(btt); const char *devname = ndctl_btt_get_devname(btt); struct ndctl_ctx *ctx = ndctl_btt_get_ctx(btt); char *path = btt->btt_buf; int len = btt->buf_len; if (ndctl_btt_is_enabled(btt)) return 0; util_bind(devname, btt->module, "nd", ctx); if (!ndctl_btt_is_enabled(btt)) { err(ctx, "%s: failed to enable\n", devname); return -ENXIO; } dbg(ctx, "%s: enabled\n", devname); if (snprintf(path, len, "%s/block", btt->btt_path) >= len) { err(ctx, "%s: buffer too small!\n", devname); } else { btt->bdev = get_block_device(ctx, path); } /* * Rescan now as successfully enabling a btt device leads to a * new one being created, and potentially the backing namespace * as well. */ region_refresh_children(region); return 0; } NDCTL_EXPORT int ndctl_btt_delete(struct ndctl_btt *btt) { struct ndctl_region *region = ndctl_btt_get_region(btt); struct ndctl_ctx *ctx = ndctl_btt_get_ctx(btt); int rc; if (!ndctl_btt_is_valid(btt)) { free_btt(btt, ®ion->stale_btts); return 0; } util_unbind(btt->btt_path, ctx); rc = ndctl_btt_set_namespace(btt, NULL); if (rc) { dbg(ctx, "%s: failed to clear namespace: %d\n", ndctl_btt_get_devname(btt), rc); return rc; } free_btt(btt, ®ion->btts); region->btts_init = 0; return 0; } NDCTL_EXPORT int ndctl_btt_is_configured(struct ndctl_btt *btt) { if (ndctl_btt_get_namespace(btt)) return 1; if (ndctl_btt_get_sector_size(btt) != UINT_MAX) return 1; if (memcmp(&btt->uuid, null_uuid, sizeof(null_uuid)) != 0) return 1; return 0; } static void *__add_pfn(struct ndctl_pfn *pfn, const char *pfn_base) { struct ndctl_ctx *ctx = ndctl_region_get_ctx(pfn->region); char *path = calloc(1, strlen(pfn_base) + 100); struct ndctl_region *region = pfn->region; char buf[SYSFS_ATTR_SIZE]; if (!path) return NULL; pfn->generation = region->generation; pfn->pfn_path = strdup(pfn_base); if (!pfn->pfn_path) goto err_read; pfn->pfn_buf = calloc(1, strlen(pfn_base) + 50); if (!pfn->pfn_buf) goto err_read; pfn->buf_len = strlen(pfn_base) + 50; sprintf(path, "%s/modalias", pfn_base); if (sysfs_read_attr(ctx, path, buf) < 0) goto err_read; pfn->module = util_modalias_to_module(ctx, buf); sprintf(path, "%s/uuid", pfn_base); if (sysfs_read_attr(ctx, path, buf) < 0) goto err_read; if (strlen(buf) && uuid_parse(buf, pfn->uuid) < 0) goto err_read; sprintf(path, "%s/mode", pfn_base); if (sysfs_read_attr(ctx, path, buf) < 0) goto err_read; if (strcmp(buf, "none") == 0) pfn->loc = NDCTL_PFN_LOC_NONE; else if (strcmp(buf, "ram") == 0) pfn->loc = NDCTL_PFN_LOC_RAM; else if (strcmp(buf, "pmem") == 0) pfn->loc = NDCTL_PFN_LOC_PMEM; else goto err_read; sprintf(path, "%s/align", pfn_base); if (sysfs_read_attr(ctx, path, buf) < 0) pfn->align = 0; else pfn->align = strtoul(buf, NULL, 0); sprintf(path, "%s/resource", pfn_base); if (sysfs_read_attr(ctx, path, buf) < 0) pfn->resource = ULLONG_MAX; else pfn->resource = strtoull(buf, NULL, 0); sprintf(path, "%s/size", pfn_base); if (sysfs_read_attr(ctx, path, buf) < 0) pfn->size = ULLONG_MAX; else pfn->size = strtoull(buf, NULL, 0); /* * The supported_alignments attribute was added before arches other * than x86 had pmem support. If the kernel doesn't provide the * attribute then it's safe to assume that we running on x86 where * 4KiB and 2MiB have always been supported. */ sprintf(path, "%s/supported_alignments", pfn_base); if (sysfs_read_attr(ctx, path, buf) < 0) sprintf(buf, "%d %d", SZ_4K, SZ_2M); if (parse_lbasize_supported(ctx, pfn_base, buf, &pfn->alignments) < 0) goto err_read; free(path); return pfn; err_read: free(pfn->pfn_buf); free(pfn->pfn_path); free(path); return NULL; } static void *add_pfn(void *parent, int id, const char *pfn_base) { struct ndctl_pfn *pfn = calloc(1, sizeof(*pfn)), *pfn_dup; struct ndctl_region *region = parent; if (!pfn) return NULL; pfn->id = id; pfn->region = region; if (!__add_pfn(pfn, pfn_base)) { free(pfn); return NULL; } ndctl_pfn_foreach(region, pfn_dup) if (pfn->id == pfn_dup->id) { pfn_dup->resource = pfn->resource; pfn_dup->size = pfn->size; free_pfn(pfn, NULL); return pfn_dup; } list_add(®ion->pfns, &pfn->list); return pfn; } static void *add_dax(void *parent, int id, const char *dax_base) { struct ndctl_dax *dax = calloc(1, sizeof(*dax)), *dax_dup; struct ndctl_region *region = parent; struct ndctl_pfn *pfn = &dax->pfn; if (!dax) return NULL; pfn->id = id; pfn->region = region; if (!__add_pfn(pfn, dax_base)) { free(dax); return NULL; } ndctl_dax_foreach(region, dax_dup) { struct ndctl_pfn *pfn_dup = &dax_dup->pfn; if (pfn->id == pfn_dup->id) { pfn_dup->resource = pfn->resource; pfn_dup->size = pfn->size; free_dax(dax, NULL); return dax_dup; } } list_add(®ion->daxs, &dax->pfn.list); return dax; } NDCTL_EXPORT struct ndctl_pfn *ndctl_pfn_get_first(struct ndctl_region *region) { pfns_init(region); return list_top(®ion->pfns, struct ndctl_pfn, list); } NDCTL_EXPORT struct ndctl_pfn *ndctl_pfn_get_next(struct ndctl_pfn *pfn) { struct ndctl_region *region = pfn->region; return list_next(®ion->pfns, pfn, list); } NDCTL_EXPORT unsigned int ndctl_pfn_get_id(struct ndctl_pfn *pfn) { return pfn->id; } NDCTL_EXPORT struct ndctl_namespace *ndctl_pfn_get_namespace(struct ndctl_pfn *pfn) { struct ndctl_ctx *ctx = ndctl_pfn_get_ctx(pfn); struct ndctl_namespace *ndns, *found = NULL; struct ndctl_region *region = pfn->region; char *path = region->region_buf; int len = region->buf_len; char buf[SYSFS_ATTR_SIZE]; if (pfn->ndns) return pfn->ndns; if (snprintf(path, len, "%s/namespace", pfn->pfn_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_pfn_get_devname(pfn)); return NULL; } if (sysfs_read_attr(ctx, path, buf) < 0) return NULL; ndctl_namespace_foreach(region, ndns) if (strcmp(buf, ndctl_namespace_get_devname(ndns)) == 0) found = ndns; pfn->ndns = found; return found; } NDCTL_EXPORT void ndctl_pfn_get_uuid(struct ndctl_pfn *pfn, uuid_t uu) { memcpy(uu, pfn->uuid, sizeof(uuid_t)); } NDCTL_EXPORT unsigned long long ndctl_pfn_get_size(struct ndctl_pfn *pfn) { return pfn->size; } NDCTL_EXPORT unsigned long long ndctl_pfn_get_resource(struct ndctl_pfn *pfn) { return pfn->resource; } NDCTL_EXPORT int ndctl_pfn_set_uuid(struct ndctl_pfn *pfn, uuid_t uu) { struct ndctl_ctx *ctx = ndctl_pfn_get_ctx(pfn); int len = pfn->buf_len, rc; char *path = pfn->pfn_buf; char uuid[40]; if (snprintf(path, len, "%s/uuid", pfn->pfn_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_pfn_get_devname(pfn)); return -ENXIO; } uuid_unparse(uu, uuid); rc = sysfs_write_attr(ctx, path, uuid); if (rc != 0) return rc; memcpy(pfn->uuid, uu, sizeof(uuid_t)); return 0; } NDCTL_EXPORT enum ndctl_pfn_loc ndctl_pfn_get_location(struct ndctl_pfn *pfn) { return pfn->loc; } NDCTL_EXPORT int ndctl_pfn_set_location(struct ndctl_pfn *pfn, enum ndctl_pfn_loc loc) { struct ndctl_ctx *ctx = ndctl_pfn_get_ctx(pfn); int len = pfn->buf_len, rc; char *path = pfn->pfn_buf; const char *locations[] = { [NDCTL_PFN_LOC_NONE] = "none", [NDCTL_PFN_LOC_RAM] = "ram", [NDCTL_PFN_LOC_PMEM] = "pmem", }; switch (loc) { case NDCTL_PFN_LOC_NONE: case NDCTL_PFN_LOC_RAM: case NDCTL_PFN_LOC_PMEM: break; default: return -EINVAL; } if (snprintf(path, len, "%s/mode", pfn->pfn_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_pfn_get_devname(pfn)); return -ENXIO; } rc = sysfs_write_attr(ctx, path, locations[loc]); if (rc != 0) return rc; pfn->loc = loc; return 0; } NDCTL_EXPORT unsigned long ndctl_pfn_get_align(struct ndctl_pfn *pfn) { return pfn->align; } NDCTL_EXPORT int ndctl_pfn_has_align(struct ndctl_pfn *pfn) { struct ndctl_ctx *ctx = ndctl_pfn_get_ctx(pfn); char *path = pfn->pfn_buf; int len = pfn->buf_len; struct stat st; if (snprintf(path, len, "%s/align", pfn->pfn_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_pfn_get_devname(pfn)); return 0; } return stat(path, &st) == 0; } NDCTL_EXPORT int ndctl_pfn_set_align(struct ndctl_pfn *pfn, unsigned long align) { struct ndctl_ctx *ctx = ndctl_pfn_get_ctx(pfn); int len = pfn->buf_len, rc; char *path = pfn->pfn_buf; char align_str[40]; if (snprintf(path, len, "%s/align", pfn->pfn_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_pfn_get_devname(pfn)); return -ENXIO; } sprintf(align_str, "%lu\n", align); rc = sysfs_write_attr(ctx, path, align_str); if (rc != 0) return rc; pfn->align = align; return 0; } NDCTL_EXPORT int ndctl_pfn_get_num_alignments(struct ndctl_pfn *pfn) { return pfn->alignments.num; } NDCTL_EXPORT unsigned long ndctl_pfn_get_supported_alignment( struct ndctl_pfn *pfn, int i) { if (pfn->alignments.num == 0) return 0; if (i < 0 || i > pfn->alignments.num) return -EINVAL; else return pfn->alignments.supported[i]; } NDCTL_EXPORT int ndctl_pfn_set_namespace(struct ndctl_pfn *pfn, struct ndctl_namespace *ndns) { struct ndctl_ctx *ctx = ndctl_pfn_get_ctx(pfn); int len = pfn->buf_len, rc; char *path = pfn->pfn_buf; if (snprintf(path, len, "%s/namespace", pfn->pfn_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_pfn_get_devname(pfn)); return -ENXIO; } rc = sysfs_write_attr(ctx, path, ndns ? ndctl_namespace_get_devname(ndns) : "\n"); if (rc != 0) return rc; pfn->ndns = ndns; return 0; } NDCTL_EXPORT struct ndctl_bus *ndctl_pfn_get_bus(struct ndctl_pfn *pfn) { return pfn->region->bus; } NDCTL_EXPORT struct ndctl_ctx *ndctl_pfn_get_ctx(struct ndctl_pfn *pfn) { return ndctl_bus_get_ctx(ndctl_pfn_get_bus(pfn)); } NDCTL_EXPORT const char *ndctl_pfn_get_devname(struct ndctl_pfn *pfn) { return devpath_to_devname(pfn->pfn_path); } NDCTL_EXPORT const char *ndctl_pfn_get_block_device(struct ndctl_pfn *pfn) { struct ndctl_ctx *ctx = ndctl_pfn_get_ctx(pfn); struct ndctl_bus *bus = ndctl_pfn_get_bus(pfn); char *path = pfn->pfn_buf; int len = pfn->buf_len; if (pfn->bdev) return pfn->bdev; if (snprintf(path, len, "%s/block", pfn->pfn_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_pfn_get_devname(pfn)); return ""; } ndctl_bus_wait_probe(bus); pfn->bdev = get_block_device(ctx, path); return pfn->bdev ? pfn->bdev : ""; } NDCTL_EXPORT int ndctl_pfn_is_valid(struct ndctl_pfn *pfn) { struct ndctl_region *region = ndctl_pfn_get_region(pfn); return pfn->generation == region->generation; } NDCTL_EXPORT int ndctl_pfn_is_enabled(struct ndctl_pfn *pfn) { struct ndctl_ctx *ctx = ndctl_pfn_get_ctx(pfn); char *path = pfn->pfn_buf; int len = pfn->buf_len; if (snprintf(path, len, "%s/driver", pfn->pfn_path) >= len) { err(ctx, "%s: buffer too small!\n", ndctl_pfn_get_devname(pfn)); return 0; } return is_enabled(ndctl_pfn_get_bus(pfn), path); } NDCTL_EXPORT struct ndctl_region *ndctl_pfn_get_region(struct ndctl_pfn *pfn) { return pfn->region; } NDCTL_EXPORT int ndctl_pfn_enable(struct ndctl_pfn *pfn) { struct ndctl_region *region = ndctl_pfn_get_region(pfn); const char *devname = ndctl_pfn_get_devname(pfn); struct ndctl_ctx *ctx = ndctl_pfn_get_ctx(pfn); char *path = pfn->pfn_buf; int len = pfn->buf_len; if (ndctl_pfn_is_enabled(pfn)) return 0; util_bind(devname, pfn->module, "nd", ctx); if (!ndctl_pfn_is_enabled(pfn)) { err(ctx, "%s: failed to enable\n", devname); return -ENXIO; } dbg(ctx, "%s: enabled\n", devname); if (snprintf(path, len, "%s/block", pfn->pfn_path) >= len) { err(ctx, "%s: buffer too small!\n", devname); } else { pfn->bdev = get_block_device(ctx, path); } /* * Rescan now as successfully enabling a pfn device leads to a * new one being created, and potentially the backing namespace * as well. */ region_refresh_children(region); return 0; } NDCTL_EXPORT int ndctl_pfn_delete(struct ndctl_pfn *pfn) { struct ndctl_region *region = ndctl_pfn_get_region(pfn); struct ndctl_ctx *ctx = ndctl_pfn_get_ctx(pfn); int rc; if (!ndctl_pfn_is_valid(pfn)) { free_pfn(pfn, ®ion->stale_pfns); return 0; } util_unbind(pfn->pfn_path, ctx); rc = ndctl_pfn_set_namespace(pfn, NULL); if (rc) { dbg(ctx, "%s: failed to clear namespace: %d\n", ndctl_pfn_get_devname(pfn), rc); return rc; } free_pfn(pfn, ®ion->pfns); region->pfns_init = 0; return 0; } NDCTL_EXPORT int ndctl_pfn_is_configured(struct ndctl_pfn *pfn) { if (ndctl_pfn_get_namespace(pfn)) return 1; if (ndctl_pfn_get_location(pfn) != NDCTL_PFN_LOC_NONE) return 1; if (memcmp(&pfn->uuid, null_uuid, sizeof(null_uuid)) != 0) return 1; return 0; } NDCTL_EXPORT struct ndctl_dax *ndctl_dax_get_first(struct ndctl_region *region) { daxs_init(region); return list_top(®ion->daxs, struct ndctl_dax, pfn.list); } NDCTL_EXPORT struct ndctl_dax *ndctl_dax_get_next(struct ndctl_dax *dax) { struct ndctl_region *region = dax->pfn.region; return list_next(®ion->daxs, dax, pfn.list); } NDCTL_EXPORT unsigned int ndctl_dax_get_id(struct ndctl_dax *dax) { return ndctl_pfn_get_id(&dax->pfn); } NDCTL_EXPORT struct ndctl_namespace *ndctl_dax_get_namespace(struct ndctl_dax *dax) { return ndctl_pfn_get_namespace(&dax->pfn); } NDCTL_EXPORT void ndctl_dax_get_uuid(struct ndctl_dax *dax, uuid_t uu) { ndctl_pfn_get_uuid(&dax->pfn, uu); } NDCTL_EXPORT unsigned long long ndctl_dax_get_size(struct ndctl_dax *dax) { return ndctl_pfn_get_size(&dax->pfn); } NDCTL_EXPORT unsigned long long ndctl_dax_get_resource(struct ndctl_dax *dax) { return ndctl_pfn_get_resource(&dax->pfn); } NDCTL_EXPORT int ndctl_dax_set_uuid(struct ndctl_dax *dax, uuid_t uu) { return ndctl_pfn_set_uuid(&dax->pfn, uu); } NDCTL_EXPORT enum ndctl_pfn_loc ndctl_dax_get_location(struct ndctl_dax *dax) { return ndctl_pfn_get_location(&dax->pfn); } NDCTL_EXPORT int ndctl_dax_set_location(struct ndctl_dax *dax, enum ndctl_pfn_loc loc) { return ndctl_pfn_set_location(&dax->pfn, loc); } NDCTL_EXPORT unsigned long ndctl_dax_get_align(struct ndctl_dax *dax) { return ndctl_pfn_get_align(&dax->pfn); } NDCTL_EXPORT int ndctl_dax_get_num_alignments(struct ndctl_dax *dax) { return ndctl_pfn_get_num_alignments(&dax->pfn); } NDCTL_EXPORT unsigned long ndctl_dax_get_supported_alignment( struct ndctl_dax *dax, int i) { return ndctl_pfn_get_supported_alignment(&dax->pfn, i); } NDCTL_EXPORT int ndctl_dax_has_align(struct ndctl_dax *dax) { return ndctl_pfn_has_align(&dax->pfn); } NDCTL_EXPORT int ndctl_dax_set_align(struct ndctl_dax *dax, unsigned long align) { return ndctl_pfn_set_align(&dax->pfn, align); } NDCTL_EXPORT int ndctl_dax_set_namespace(struct ndctl_dax *dax, struct ndctl_namespace *ndns) { return ndctl_pfn_set_namespace(&dax->pfn, ndns); } NDCTL_EXPORT struct ndctl_bus *ndctl_dax_get_bus(struct ndctl_dax *dax) { return ndctl_pfn_get_bus(&dax->pfn); } NDCTL_EXPORT struct ndctl_ctx *ndctl_dax_get_ctx(struct ndctl_dax *dax) { return ndctl_pfn_get_ctx(&dax->pfn); } NDCTL_EXPORT const char *ndctl_dax_get_devname(struct ndctl_dax *dax) { return ndctl_pfn_get_devname(&dax->pfn); } NDCTL_EXPORT int ndctl_dax_is_valid(struct ndctl_dax *dax) { return ndctl_pfn_is_valid(&dax->pfn); } NDCTL_EXPORT int ndctl_dax_is_enabled(struct ndctl_dax *dax) { return ndctl_pfn_is_enabled(&dax->pfn); } NDCTL_EXPORT struct ndctl_region *ndctl_dax_get_region(struct ndctl_dax *dax) { return ndctl_pfn_get_region(&dax->pfn); } NDCTL_EXPORT int ndctl_dax_enable(struct ndctl_dax *dax) { struct ndctl_region *region = ndctl_dax_get_region(dax); const char *devname = ndctl_dax_get_devname(dax); struct ndctl_ctx *ctx = ndctl_dax_get_ctx(dax); struct ndctl_pfn *pfn = &dax->pfn; if (ndctl_dax_is_enabled(dax)) return 0; util_bind(devname, pfn->module, "nd", ctx); if (!ndctl_dax_is_enabled(dax)) { err(ctx, "%s: failed to enable\n", devname); return -ENXIO; } dbg(ctx, "%s: enabled\n", devname); /* * Rescan now as successfully enabling a dax device leads to a * new one being created, and potentially the backing namespace * as well. */ region_refresh_children(region); return 0; } NDCTL_EXPORT int ndctl_dax_delete(struct ndctl_dax *dax) { struct ndctl_region *region = ndctl_dax_get_region(dax); struct ndctl_ctx *ctx = ndctl_dax_get_ctx(dax); struct ndctl_pfn *pfn = &dax->pfn; int rc; if (!ndctl_dax_is_valid(dax)) { free_dax(dax, ®ion->stale_daxs); return 0; } util_unbind(pfn->pfn_path, ctx); rc = ndctl_dax_set_namespace(dax, NULL); if (rc) { dbg(ctx, "%s: failed to clear namespace: %d\n", ndctl_dax_get_devname(dax), rc); return rc; } free_dax(dax, ®ion->daxs); region->daxs_init = 0; return 0; } NDCTL_EXPORT int ndctl_dax_is_configured(struct ndctl_dax *dax) { return ndctl_pfn_is_configured(&dax->pfn); } NDCTL_EXPORT struct daxctl_region *ndctl_dax_get_daxctl_region( struct ndctl_dax *dax) { struct ndctl_ctx *ctx = ndctl_dax_get_ctx(dax); struct ndctl_region *region; uuid_t uuid; int id; if (dax->region) return dax->region; region = ndctl_dax_get_region(dax); id = ndctl_region_get_id(region); ndctl_dax_get_uuid(dax, uuid); dax->region = daxctl_new_region(ctx->daxctl_ctx, id, uuid, dax->pfn.pfn_path); return dax->region; } ndctl-81/ndctl/lib/libndctl.pc.in000066400000000000000000000003751476737544500170410ustar00rootroot00000000000000prefix=@prefix@ exec_prefix=@exec_prefix@ libdir=@libdir@ includedir=@includedir@ Name: libndctl Description: Manage "libnvdimm" subsystem devices (Non-volatile Memory) Version: @VERSION@ Libs: -L${libdir} -lndctl Libs.private: Cflags: -I${includedir} ndctl-81/ndctl/lib/libndctl.sym000066400000000000000000000276721476737544500166530ustar00rootroot00000000000000LIBNDCTL_1 { global: ndctl_min_namespace_size; ndctl_sizeof_namespace_index; ndctl_sizeof_namespace_label; ndctl_get_userdata; ndctl_set_userdata; ndctl_ref; ndctl_get_log_priority; ndctl_set_log_fn; ndctl_unref; ndctl_set_log_priority; ndctl_new; ndctl_get_daxctl_ctx; ndctl_set_private_data; ndctl_get_private_data; ndctl_invalidate; local: *; }; LIBNDCTL_3 { global: ndctl_bus_get_first; ndctl_bus_get_next; ndctl_bus_get_ctx; ndctl_bus_get_major; ndctl_bus_get_minor; ndctl_bus_get_devname; ndctl_bus_get_by_provider; ndctl_bus_get_cmd_name; ndctl_bus_is_cmd_supported; ndctl_bus_has_nfit; ndctl_bus_get_revision; ndctl_bus_get_id; ndctl_bus_get_provider; ndctl_bus_get_ctx; ndctl_bus_wait_probe; ndctl_dimm_get_first; ndctl_dimm_get_next; ndctl_dimm_get_handle; ndctl_dimm_get_phys_id; ndctl_dimm_get_vendor; ndctl_dimm_get_device; ndctl_dimm_get_revision; ndctl_dimm_get_manufacturing_date; ndctl_dimm_get_manufacturing_location; ndctl_dimm_get_subsystem_vendor; ndctl_dimm_get_subsystem_device; ndctl_dimm_get_subsystem_revision; ndctl_dimm_get_format; ndctl_dimm_get_formats; ndctl_dimm_get_formatN; ndctl_dimm_get_major; ndctl_dimm_get_minor; ndctl_dimm_get_serial; ndctl_dimm_get_id; ndctl_dimm_get_unique_id; ndctl_dimm_get_devname; ndctl_dimm_get_cmd_name; ndctl_dimm_has_errors; ndctl_dimm_has_notifications; ndctl_dimm_failed_save; ndctl_dimm_failed_map; ndctl_dimm_failed_arm; ndctl_dimm_failed_restore; ndctl_dimm_smart_pending; ndctl_dimm_failed_flush; ndctl_dimm_get_health_eventfd; ndctl_dimm_is_cmd_supported; ndctl_dimm_handle_get_node; ndctl_dimm_handle_get_socket; ndctl_dimm_handle_get_imc; ndctl_dimm_handle_get_channel; ndctl_dimm_handle_get_dimm; ndctl_dimm_get_bus; ndctl_dimm_get_ctx; ndctl_dimm_get_by_handle; ndctl_dimm_is_active; ndctl_dimm_is_enabled; ndctl_dimm_disable; ndctl_dimm_enable; ndctl_bus_cmd_new_ars_cap; ndctl_bus_cmd_new_ars_start; ndctl_bus_cmd_new_ars_status; ndctl_cmd_ars_cap_get_size; ndctl_cmd_ars_cap_get_range; ndctl_cmd_ars_in_progress; ndctl_cmd_ars_num_records; ndctl_cmd_ars_get_record_addr; ndctl_cmd_ars_get_record_len; ndctl_bus_cmd_new_clear_error; ndctl_cmd_clear_error_get_cleared; ndctl_dimm_cmd_new_vendor_specific; ndctl_cmd_vendor_set_input; ndctl_cmd_vendor_get_output_size; ndctl_cmd_vendor_get_output; ndctl_dimm_cmd_new_cfg_size; ndctl_dimm_cmd_new_cfg_read; ndctl_dimm_cmd_new_cfg_write; ndctl_dimm_cmd_new_smart; ndctl_cmd_smart_get_flags; ndctl_cmd_smart_get_health; ndctl_cmd_smart_get_temperature; ndctl_cmd_smart_get_spares; ndctl_cmd_smart_get_alarm_flags; ndctl_cmd_smart_get_life_used; ndctl_cmd_smart_get_shutdown_state; ndctl_cmd_smart_get_vendor_size; ndctl_cmd_smart_get_vendor_data; ndctl_dimm_cmd_new_smart_threshold; ndctl_cmd_smart_threshold_get_alarm_control; ndctl_cmd_smart_threshold_get_temperature; ndctl_cmd_smart_threshold_get_spares; ndctl_dimm_zero_labels; ndctl_dimm_get_available_labels; ndctl_region_get_first; ndctl_region_get_next; ndctl_region_get_id; ndctl_region_get_devname; ndctl_region_get_interleave_ways; ndctl_region_get_range_index; ndctl_region_get_mappings; ndctl_region_get_size; ndctl_region_get_available_size; ndctl_region_get_type; ndctl_region_get_namespace_seed; ndctl_region_get_btt_seed; ndctl_region_get_type_name; ndctl_region_get_bus; ndctl_region_get_ctx; ndctl_region_get_first_dimm; ndctl_region_get_next_dimm; ndctl_region_is_enabled; ndctl_region_enable; ndctl_region_disable_invalidate; ndctl_region_disable_preserve; ndctl_region_cleanup; ndctl_region_get_interleave_set; ndctl_region_get_ro; ndctl_region_set_ro; ndctl_region_get_resource; ndctl_region_get_first_badblock; ndctl_region_get_next_badblock; ndctl_interleave_set_get_first; ndctl_interleave_set_get_next; ndctl_interleave_set_is_active; ndctl_interleave_set_get_cookie; ndctl_interleave_set_get_region; ndctl_interleave_set_get_first_dimm; ndctl_interleave_set_get_next_dimm; ndctl_mapping_get_first; ndctl_mapping_get_next; ndctl_mapping_get_dimm; ndctl_mapping_get_ctx; ndctl_mapping_get_bus; ndctl_mapping_get_region; ndctl_mapping_get_offset; ndctl_mapping_get_length; ndctl_namespace_get_first; ndctl_namespace_get_next; ndctl_namespace_get_ctx; ndctl_namespace_get_bus; ndctl_namespace_get_btt; ndctl_namespace_get_pfn; ndctl_namespace_get_dax; ndctl_namespace_get_region; ndctl_namespace_get_id; ndctl_namespace_get_devname; ndctl_namespace_get_block_device; ndctl_namespace_get_mode; ndctl_region_get_nstype; ndctl_namespace_get_type; ndctl_namespace_get_type_name; ndctl_namespace_is_enabled; ndctl_namespace_enable; ndctl_namespace_disable; ndctl_namespace_disable_invalidate; ndctl_namespace_disable_safe; ndctl_namespace_is_active; ndctl_namespace_is_valid; ndctl_namespace_is_configured; ndctl_namespace_delete; ndctl_namespace_set_uuid; ndctl_namespace_get_uuid; ndctl_namespace_get_alt_name; ndctl_namespace_set_alt_name; ndctl_namespace_get_size; ndctl_namespace_set_size; ndctl_namespace_get_resource; ndctl_namespace_get_supported_sector_size; ndctl_namespace_get_sector_size; ndctl_namespace_get_num_sector_sizes; ndctl_namespace_set_sector_size; ndctl_namespace_get_raw_mode; ndctl_namespace_set_raw_mode; ndctl_namespace_get_numa_node; ndctl_btt_get_first; ndctl_btt_get_next; ndctl_btt_get_ctx; ndctl_btt_get_bus; ndctl_btt_get_region; ndctl_btt_get_id; ndctl_btt_get_supported_sector_size; ndctl_btt_get_sector_size; ndctl_btt_get_num_sector_sizes; ndctl_btt_get_namespace; ndctl_btt_get_uuid; ndctl_btt_get_size; ndctl_btt_is_enabled; ndctl_btt_is_valid; ndctl_btt_get_devname; ndctl_btt_get_block_device; ndctl_btt_set_uuid; ndctl_btt_set_sector_size; ndctl_btt_set_namespace; ndctl_btt_enable; ndctl_btt_delete; ndctl_btt_is_configured; ndctl_cmd_cfg_size_get_size; ndctl_cmd_cfg_read_get_data; ndctl_cmd_cfg_read_get_size; ndctl_cmd_cfg_write_set_data; ndctl_cmd_cfg_write_zero_data; ndctl_cmd_unref; ndctl_cmd_ref; ndctl_cmd_get_type; ndctl_cmd_get_status; ndctl_cmd_get_firmware_status; ndctl_cmd_submit; ndctl_region_get_pfn_seed; ndctl_pfn_get_first; ndctl_pfn_get_next; ndctl_pfn_get_id; ndctl_pfn_get_namespace; ndctl_pfn_get_uuid; ndctl_pfn_set_uuid; ndctl_pfn_get_location; ndctl_pfn_set_location; ndctl_pfn_get_align; ndctl_pfn_get_size; ndctl_pfn_get_resource; ndctl_pfn_has_align; ndctl_pfn_set_align; ndctl_pfn_set_namespace; ndctl_pfn_get_bus; ndctl_pfn_get_ctx; ndctl_pfn_get_devname; ndctl_pfn_get_block_device; ndctl_pfn_is_valid; ndctl_pfn_is_enabled; ndctl_pfn_get_region; ndctl_pfn_enable; ndctl_pfn_delete; ndctl_pfn_is_configured; ndctl_region_get_dax_seed; ndctl_namespace_get_dax; ndctl_dax_get_first; ndctl_dax_get_next; ndctl_dax_get_id; ndctl_dax_get_namespace; ndctl_dax_get_uuid; ndctl_dax_get_size; ndctl_dax_get_resource; ndctl_dax_set_uuid; ndctl_dax_get_location; ndctl_dax_set_location; ndctl_dax_get_align; ndctl_dax_has_align; ndctl_dax_set_align; ndctl_dax_set_namespace; ndctl_dax_get_bus; ndctl_dax_get_ctx; ndctl_dax_get_devname; ndctl_dax_is_valid; ndctl_dax_is_enabled; ndctl_dax_get_region; ndctl_dax_enable; ndctl_dax_delete; ndctl_dax_is_configured; ndctl_dax_get_daxctl_region; } LIBNDCTL_1; LIBNDCTL_13 { global: ndctl_bus_get_region_by_physical_address; ndctl_bus_get_dimm_by_physical_address; ndctl_bus_is_nfit_cmd_supported; ndctl_dimm_read_labels; ndctl_dimm_validate_labels; ndctl_dimm_init_labels; ndctl_dimm_sizeof_namespace_label; ndctl_mapping_get_position; ndctl_namespace_set_enforce_mode; ndctl_namespace_get_enforce_mode; } LIBNDCTL_3; LIBNDCTL_14 { global: ndctl_dimm_locked; ndctl_dimm_aliased; ndctl_cmd_smart_get_shutdown_count; ndctl_bus_wait_for_scrub_completion; ndctl_bus_get_scrub_count; ndctl_bus_has_error_injection; ndctl_namespace_inject_error; ndctl_namespace_uninject_error; ndctl_namespace_injection_status; ndctl_namespace_injection_get_first_bb; ndctl_namespace_injection_get_next_bb; ndctl_bb_get_block; ndctl_bb_get_count; ndctl_cmd_smart_get_media_temperature; ndctl_cmd_smart_threshold_get_media_temperature; ndctl_cmd_smart_get_ctrl_temperature; ndctl_cmd_smart_threshold_get_ctrl_temperature; ndctl_dimm_cmd_new_smart_set_threshold; ndctl_cmd_smart_threshold_get_supported_alarms; ndctl_cmd_smart_threshold_set_alarm_control; ndctl_cmd_smart_threshold_set_temperature; ndctl_cmd_smart_threshold_set_media_temperature; ndctl_cmd_smart_threshold_set_ctrl_temperature; ndctl_cmd_smart_threshold_set_spares; ndctl_decode_smart_temperature; ndctl_encode_smart_temperature; ndctl_dimm_cmd_new_smart_inject; ndctl_cmd_smart_inject_media_temperature; ndctl_cmd_smart_inject_spares; ndctl_cmd_smart_inject_fatal; ndctl_cmd_smart_inject_unsafe_shutdown; ndctl_dimm_cmd_new_fw_get_info; ndctl_dimm_cmd_new_fw_start_update; ndctl_dimm_cmd_new_fw_send; ndctl_dimm_cmd_new_fw_finish; ndctl_dimm_cmd_new_fw_abort; ndctl_dimm_cmd_new_fw_finish_query; ndctl_cmd_fw_info_get_storage_size; ndctl_cmd_fw_info_get_max_send_len; ndctl_cmd_fw_info_get_query_interval; ndctl_cmd_fw_info_get_max_query_time; ndctl_cmd_fw_info_get_run_version; ndctl_cmd_fw_info_get_updated_version; ndctl_cmd_fw_start_get_context; ndctl_cmd_fw_fquery_get_fw_rev; ndctl_cmd_fw_xlat_firmware_status; } LIBNDCTL_13; LIBNDCTL_15 { global: ndctl_dimm_cmd_new_ack_shutdown_count; ndctl_region_get_numa_node; ndctl_dimm_fw_update_supported; ndctl_region_get_persistence_domain; ndctl_bus_get_persistence_domain; ndctl_namespace_write_cache_is_enabled; ndctl_namespace_enable_write_cache; ndctl_namespace_disable_write_cache; ndctl_bus_get_scrub_state; ndctl_bus_start_scrub; ndctl_region_deep_flush; } LIBNDCTL_14; LIBNDCTL_16 { global: ndctl_cmd_ars_cap_get_clear_unit; ndctl_namespace_inject_error2; ndctl_namespace_uninject_error2; ndctl_cmd_ars_stat_get_flag_overflow; } LIBNDCTL_15; LIBNDCTL_17 { global: ndctl_dimm_smart_inject_supported; ndctl_dimm_get_health; ndctl_dimm_get_flags; ndctl_dimm_get_event_flags; ndctl_dimm_is_flag_supported; ndctl_region_get_max_available_extent; ndctl_cmd_smart_inject_ctrl_temperature; } LIBNDCTL_16; LIBNDCTL_18 { global: ndctl_namespace_get_first_badblock; ndctl_namespace_get_next_badblock; ndctl_dimm_get_dirty_shutdown; } LIBNDCTL_17; LIBNDCTL_19 { global: ndctl_cmd_xlat_firmware_status; ndctl_cmd_submit_xlat; ndctl_pfn_get_supported_alignment; ndctl_pfn_get_num_alignments; ndctl_dax_get_supported_alignment; ndctl_dax_get_num_alignments; ndctl_dimm_get_security; ndctl_dimm_update_passphrase; ndctl_dimm_disable_passphrase; ndctl_dimm_freeze_security; ndctl_dimm_secure_erase; ndctl_dimm_overwrite; ndctl_dimm_wait_overwrite; ndctl_dimm_update_master_passphrase; ndctl_dimm_master_secure_erase; } LIBNDCTL_18; LIBNDCTL_20 { global: ndctl_bus_poll_scrub_completion; } LIBNDCTL_19; LIBNDCTL_21 { ndctl_cmd_cfg_read_set_extent; ndctl_cmd_cfg_write_set_extent; ndctl_dimm_read_label_index; ndctl_dimm_read_label_extent; ndctl_dimm_zero_label_extent; } LIBNDCTL_20; LIBNDCTL_22 { ndctl_dimm_security_is_frozen; } LIBNDCTL_21; LIBNDCTL_23 { ndctl_namespace_is_configuration_idle; ndctl_namespace_get_target_node; ndctl_region_get_target_node; ndctl_region_get_align; ndctl_region_set_align; } LIBNDCTL_22; LIBNDCTL_24 { ndctl_bus_has_of_node; ndctl_bus_is_papr_scm; ndctl_region_has_numa; } LIBNDCTL_23; LIBNDCTL_25 { ndctl_dimm_get_fw_activate_state; ndctl_dimm_get_fw_activate_result; ndctl_bus_get_fw_activate_state; ndctl_bus_get_fw_activate_method; ndctl_dimm_fw_activate_disarm; ndctl_dimm_fw_activate_arm; ndctl_bus_set_fw_activate_noidle; ndctl_bus_clear_fw_activate_noidle; ndctl_bus_set_fw_activate_nosuspend; ndctl_bus_clear_fw_activate_nosuspend; ndctl_bus_activate_firmware; } LIBNDCTL_24; LIBNDCTL_26 { ndctl_bus_nfit_translate_spa; ndctl_dimm_sizeof_namespace_index; ndctl_set_config_path; ndctl_get_config_path; } LIBNDCTL_25; LIBNDCTL_27 { ndctl_dimm_refresh_flags; } LIBNDCTL_26; LIBNDCTL_28 { ndctl_dimm_disable_master_passphrase; ndctl_bus_has_cxl; } LIBNDCTL_27; ndctl-81/ndctl/lib/meson.build000066400000000000000000000017651476737544500164630ustar00rootroot00000000000000libndctl_version = '@0@.@1@.@2@'.format( LIBNDCTL_CURRENT - LIBNDCTL_AGE, LIBNDCTL_REVISION, LIBNDCTL_AGE) libndctl_dir_path = meson.current_source_dir() libndctl_sym = files('libndctl.sym') libndctl_sym_path = libndctl_dir_path / 'libndctl.sym' ndctl = library( 'ndctl', '../../util/log.c', '../../util/sysfs.c', 'dimm.c', 'inject.c', 'nfit.c', 'smart.c', 'intel.c', 'hpe1.c', 'msft.c', 'hyperv.c', 'papr.c', 'ars.c', 'firmware.c', 'libndctl.c', dependencies : [ daxctl_dep, libudev, uuid, kmod, ], include_directories : root_inc, version : libndctl_version, install : true, install_dir : rootlibdir, link_args : '-Wl,--version-script=' + libndctl_sym_path, link_depends : libndctl_sym, ) ndctl_dep = declare_dependency(link_with : ndctl) custom_target( 'libndctl.pc', command : pkgconfig_script + [ '@INPUT@' ], input : 'libndctl.pc.in', output : 'libndctl.pc', capture : true, install : true, install_dir : pkgconfiglibdir, ) ndctl-81/ndctl/lib/msft.c000066400000000000000000000122271476737544500154310ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-2.1 // Copyright (C) 2016-2017 Dell, Inc. // Copyright (C) 2016 Hewlett Packard Enterprise Development LP // Copyright (C) 2016-2020, Intel Corporation. /* Copyright (C) 2022 iXsystems, Inc. */ #include #include #include #include #include "private.h" #include "msft.h" #define CMD_MSFT(_c) ((_c)->msft) #define CMD_MSFT_SMART(_c) (CMD_MSFT(_c)->u.smart.data) static const char *msft_cmd_desc(int fn) { static const char * const descs[] = { [NDN_MSFT_CMD_CHEALTH] = "critical_health", [NDN_MSFT_CMD_NHEALTH] = "nvdimm_health", [NDN_MSFT_CMD_EHEALTH] = "es_health", }; const char *desc; if (fn >= (int) ARRAY_SIZE(descs)) return "unknown"; desc = descs[fn]; if (!desc) return "unknown"; return desc; } static bool msft_cmd_is_supported(struct ndctl_dimm *dimm, int cmd) { /* Handle this separately to support monitor mode */ if (cmd == ND_CMD_SMART) return true; return !!(dimm->cmd_mask & (1ULL << cmd)); } static u32 msft_get_firmware_status(struct ndctl_cmd *cmd) { return cmd->msft->u.smart.status; } static struct ndctl_cmd *alloc_msft_cmd(struct ndctl_dimm *dimm, unsigned int func, size_t in_size, size_t out_size) { struct ndctl_bus *bus = ndctl_dimm_get_bus(dimm); struct ndctl_ctx *ctx = ndctl_bus_get_ctx(bus); struct ndctl_cmd *cmd; size_t size; struct ndn_pkg_msft *msft; if (!ndctl_dimm_is_cmd_supported(dimm, ND_CMD_CALL)) { dbg(ctx, "unsupported cmd\n"); return NULL; } if (test_dimm_dsm(dimm, func) == DIMM_DSM_UNSUPPORTED) { dbg(ctx, "unsupported function\n"); return NULL; } size = sizeof(*cmd) + sizeof(struct nd_cmd_pkg) + in_size + out_size; cmd = calloc(1, size); if (!cmd) return NULL; cmd->dimm = dimm; ndctl_cmd_ref(cmd); cmd->type = ND_CMD_CALL; cmd->size = size; cmd->status = 1; msft = CMD_MSFT(cmd); msft->gen.nd_family = NVDIMM_FAMILY_MSFT; msft->gen.nd_command = func; msft->gen.nd_fw_size = 0; msft->gen.nd_size_in = in_size; msft->gen.nd_size_out = out_size; msft->u.smart.status = 0; cmd->get_firmware_status = msft_get_firmware_status; return cmd; } static struct ndctl_cmd *msft_dimm_cmd_new_smart(struct ndctl_dimm *dimm) { return alloc_msft_cmd(dimm, NDN_MSFT_CMD_NHEALTH, 0, sizeof(struct ndn_msft_smart)); } static int msft_smart_valid(struct ndctl_cmd *cmd) { if (cmd->type != ND_CMD_CALL || CMD_MSFT(cmd)->gen.nd_family != NVDIMM_FAMILY_MSFT || CMD_MSFT(cmd)->gen.nd_command != NDN_MSFT_CMD_NHEALTH || cmd->status != 0) return cmd->status < 0 ? cmd->status : -EINVAL; return 0; } static unsigned int msft_cmd_smart_get_flags(struct ndctl_cmd *cmd) { int rc; rc = msft_smart_valid(cmd); if (rc < 0) { errno = -rc; return UINT_MAX; } /* below health data can be retrieved via MSFT _DSM function 11 */ return ND_SMART_HEALTH_VALID | ND_SMART_TEMP_VALID | ND_SMART_USED_VALID | ND_SMART_ALARM_VALID; } static unsigned int msft_cmd_smart_get_health(struct ndctl_cmd *cmd) { unsigned int health = 0; int rc; rc = msft_smart_valid(cmd); if (rc < 0) { errno = -rc; return UINT_MAX; } if (CMD_MSFT_SMART(cmd)->nvm_lifetime == 0) health |= ND_SMART_FATAL_HEALTH; if (CMD_MSFT_SMART(cmd)->health != 0 || CMD_MSFT_SMART(cmd)->err_thresh_stat != 0) health |= ND_SMART_CRITICAL_HEALTH; if (CMD_MSFT_SMART(cmd)->warn_thresh_stat != 0) health |= ND_SMART_NON_CRITICAL_HEALTH; return health; } static unsigned int msft_cmd_smart_get_media_temperature(struct ndctl_cmd *cmd) { int rc; rc = msft_smart_valid(cmd); if (rc < 0) { errno = -rc; return UINT_MAX; } return CMD_MSFT_SMART(cmd)->temp * 16; } static unsigned int msft_cmd_smart_get_alarm_flags(struct ndctl_cmd *cmd) { __u8 stat; unsigned int flags = 0; int rc; rc = msft_smart_valid(cmd); if (rc < 0) { errno = -rc; return UINT_MAX; } stat = CMD_MSFT_SMART(cmd)->err_thresh_stat | CMD_MSFT_SMART(cmd)->warn_thresh_stat; if (stat & 3) /* NVM_LIFETIME/ES_LIFETIME */ flags |= ND_SMART_SPARE_TRIP; if (stat & 4) /* ES_TEMP */ flags |= ND_SMART_CTEMP_TRIP; return flags; } static unsigned int msft_cmd_smart_get_life_used(struct ndctl_cmd *cmd) { int rc; rc = msft_smart_valid(cmd); if (rc < 0) { errno = -rc; return UINT_MAX; } return 100 - CMD_MSFT_SMART(cmd)->nvm_lifetime; } static int msft_cmd_xlat_firmware_status(struct ndctl_cmd *cmd) { unsigned int status; status = cmd->get_firmware_status(cmd) & NDN_MSFT_STATUS_MASK; /* Common statuses */ switch (status) { case NDN_MSFT_STATUS_SUCCESS: return 0; case NDN_MSFT_STATUS_NOTSUPP: return -EOPNOTSUPP; case NDN_MSFT_STATUS_INVALPARM: return -EINVAL; case NDN_MSFT_STATUS_I2CERR: return -EIO; } return -ENOMSG; } struct ndctl_dimm_ops * const msft_dimm_ops = &(struct ndctl_dimm_ops) { .cmd_desc = msft_cmd_desc, .cmd_is_supported = msft_cmd_is_supported, .new_smart = msft_dimm_cmd_new_smart, .smart_get_flags = msft_cmd_smart_get_flags, .smart_get_health = msft_cmd_smart_get_health, .smart_get_media_temperature = msft_cmd_smart_get_media_temperature, .smart_get_alarm_flags = msft_cmd_smart_get_alarm_flags, .smart_get_life_used = msft_cmd_smart_get_life_used, .xlat_firmware_status = msft_cmd_xlat_firmware_status, }; ndctl-81/ndctl/lib/msft.h000066400000000000000000000023611476737544500154340ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1 */ /* Copyright (C) 2016-2017 Dell, Inc. */ /* Copyright (C) 2016 Hewlett Packard Enterprise Development LP */ /* Copyright (C) 2014-2020, Intel Corporation. */ /* Copyright (C) 2022 iXsystems, Inc. */ #ifndef __NDCTL_MSFT_H__ #define __NDCTL_MSFT_H__ enum { NDN_MSFT_CMD_CHEALTH = 10, NDN_MSFT_CMD_NHEALTH = 11, NDN_MSFT_CMD_EHEALTH = 12, }; /* * This is actually function 11 data, * This is the closest I can find to match smart * Microsoft _DSM does not have smart function */ struct ndn_msft_smart_data { __u16 health; __u16 temp; __u8 err_thresh_stat; __u8 warn_thresh_stat; __u8 nvm_lifetime; __u8 count_dram_uncorr_err; __u8 count_dram_corr_err; } __attribute__((packed)); struct ndn_msft_smart { __u32 status; union { __u8 buf[9]; struct ndn_msft_smart_data data[1]; }; } __attribute__((packed)); union ndn_msft_cmd { __u32 query; struct ndn_msft_smart smart; } __attribute__((packed)); struct ndn_pkg_msft { struct nd_cmd_pkg gen; union ndn_msft_cmd u; } __attribute__((packed)); #define NDN_MSFT_STATUS_MASK 0xffff #define NDN_MSFT_STATUS_SUCCESS 0 #define NDN_MSFT_STATUS_NOTSUPP 1 #define NDN_MSFT_STATUS_INVALPARM 2 #define NDN_MSFT_STATUS_I2CERR 3 #endif /* __NDCTL_MSFT_H__ */ ndctl-81/ndctl/lib/nfit.c000066400000000000000000000137201476737544500154170ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-2.1 // Copyright (c) 2017, FUJITSU LIMITED. All rights reserved. #include #include #include "private.h" #include static u32 bus_get_firmware_status(struct ndctl_cmd *cmd) { struct nd_cmd_bus *cmd_bus = cmd->cmd_bus; switch (cmd_bus->gen.nd_command) { case NFIT_CMD_TRANSLATE_SPA: return cmd_bus->xlat_spa.status; case NFIT_CMD_ARS_INJECT_SET: return cmd_bus->err_inj.status; case NFIT_CMD_ARS_INJECT_CLEAR: return cmd_bus->err_inj_clr.status; case NFIT_CMD_ARS_INJECT_GET: return cmd_bus->err_inj_stat.status; } return -1U; } /** * ndctl_bus_is_nfit_cmd_supported - ask nfit command is supported on @bus. * @bus: ndctl_bus instance * @cmd: nfit command number (defined as NFIT_CMD_XXX in libndctl-nfit.h) * * Return 1: command is supported. Return 0: command is not supported. * */ NDCTL_EXPORT int ndctl_bus_is_nfit_cmd_supported(struct ndctl_bus *bus, int cmd) { return !!(bus->nfit_dsm_mask & (1ULL << cmd)); } static int bus_has_translate_spa(struct ndctl_bus *bus) { if (!ndctl_bus_has_nfit(bus)) return 0; return ndctl_bus_is_nfit_cmd_supported(bus, NFIT_CMD_TRANSLATE_SPA); } static struct ndctl_cmd *ndctl_bus_cmd_new_translate_spa(struct ndctl_bus *bus) { struct ndctl_cmd *cmd; struct nd_cmd_pkg *pkg; struct nd_cmd_translate_spa *translate_spa; size_t size, spa_length; spa_length = sizeof(struct nd_cmd_translate_spa) + sizeof(struct nd_nvdimm_device); size = sizeof(*cmd) + sizeof(*pkg) + spa_length; cmd = calloc(1, size); if (!cmd) return NULL; cmd->bus = bus; ndctl_cmd_ref(cmd); cmd->type = ND_CMD_CALL; cmd->get_firmware_status = bus_get_firmware_status; cmd->size = size; cmd->status = 1; pkg = &cmd->cmd_bus->gen; pkg->nd_command = NFIT_CMD_TRANSLATE_SPA; pkg->nd_size_in = sizeof(unsigned long long); pkg->nd_size_out = spa_length; pkg->nd_fw_size = spa_length; translate_spa = &cmd->cmd_bus->xlat_spa; translate_spa->translate_length = spa_length; return cmd; } static int ndctl_bus_cmd_get_translate_spa(struct ndctl_cmd *cmd, unsigned int *handle, unsigned long long *dpa) { struct nd_cmd_pkg *pkg; struct nd_cmd_translate_spa *translate_spa; pkg = (struct nd_cmd_pkg *)&cmd->cmd_buf[0]; translate_spa = (struct nd_cmd_translate_spa *)&pkg->nd_payload[0]; if (translate_spa->status == ND_TRANSLATE_SPA_STATUS_INVALID_SPA) return -EINVAL; /* * XXX: Currently NVDIMM mirroring is not supported. * Even if ACPI returned plural dimms due to mirroring, * this function returns just the first dimm. */ *handle = translate_spa->devices[0].nfit_device_handle; *dpa = translate_spa->devices[0].dpa; return 0; } static int is_valid_spa(struct ndctl_bus *bus, unsigned long long spa) { return !!ndctl_bus_get_region_by_physical_address(bus, spa); } /** * ndctl_bus_nfit_translate_spa - call translate spa. * @bus: bus which belongs to. * @address: address (System Physical Address) * @handle: pointer to return dimm handle * @dpa: pointer to return Dimm Physical address * * If success, returns zero, store dimm's @handle, and @dpa. */ NDCTL_EXPORT int ndctl_bus_nfit_translate_spa(struct ndctl_bus *bus, unsigned long long address, unsigned int *handle, unsigned long long *dpa) { struct ndctl_cmd *cmd; struct nd_cmd_pkg *pkg; struct nd_cmd_translate_spa *translate_spa; int rc; if (!bus || !handle || !dpa) return -EINVAL; if (!bus_has_translate_spa(bus)) return -ENOTTY; if (!is_valid_spa(bus, address)) return -EINVAL; cmd = ndctl_bus_cmd_new_translate_spa(bus); if (!cmd) return -ENOMEM; pkg = (struct nd_cmd_pkg *)&cmd->cmd_buf[0]; translate_spa = (struct nd_cmd_translate_spa *)&pkg->nd_payload[0]; translate_spa->spa = address; rc = ndctl_cmd_submit(cmd); if (rc < 0) { ndctl_cmd_unref(cmd); return rc; } rc = ndctl_bus_cmd_get_translate_spa(cmd, handle, dpa); ndctl_cmd_unref(cmd); return rc; } struct ndctl_cmd *ndctl_bus_cmd_new_err_inj(struct ndctl_bus *bus) { size_t size, cmd_length; struct nd_cmd_pkg *pkg; struct ndctl_cmd *cmd; cmd_length = sizeof(struct nd_cmd_ars_err_inj); size = sizeof(*cmd) + sizeof(*pkg) + cmd_length; cmd = calloc(1, size); if (!cmd) return NULL; cmd->bus = bus; ndctl_cmd_ref(cmd); cmd->type = ND_CMD_CALL; cmd->get_firmware_status = bus_get_firmware_status; cmd->size = size; cmd->status = 1; pkg = (struct nd_cmd_pkg *)&cmd->cmd_buf[0]; pkg->nd_command = NFIT_CMD_ARS_INJECT_SET; pkg->nd_size_in = offsetof(struct nd_cmd_ars_err_inj, status); pkg->nd_size_out = cmd_length - pkg->nd_size_in; pkg->nd_fw_size = pkg->nd_size_out; return cmd; } struct ndctl_cmd *ndctl_bus_cmd_new_err_inj_clr(struct ndctl_bus *bus) { size_t size, cmd_length; struct nd_cmd_pkg *pkg; struct ndctl_cmd *cmd; cmd_length = sizeof(struct nd_cmd_ars_err_inj_clr); size = sizeof(*cmd) + sizeof(*pkg) + cmd_length; cmd = calloc(1, size); if (!cmd) return NULL; cmd->bus = bus; ndctl_cmd_ref(cmd); cmd->type = ND_CMD_CALL; cmd->get_firmware_status = bus_get_firmware_status; cmd->size = size; cmd->status = 1; pkg = (struct nd_cmd_pkg *)&cmd->cmd_buf[0]; pkg->nd_command = NFIT_CMD_ARS_INJECT_CLEAR; pkg->nd_size_in = offsetof(struct nd_cmd_ars_err_inj_clr, status); pkg->nd_size_out = cmd_length - pkg->nd_size_in; pkg->nd_fw_size = pkg->nd_size_out; return cmd; } struct ndctl_cmd *ndctl_bus_cmd_new_err_inj_stat(struct ndctl_bus *bus, u32 buf_size) { size_t size, cmd_length; struct nd_cmd_pkg *pkg; struct ndctl_cmd *cmd; cmd_length = sizeof(struct nd_cmd_ars_err_inj_stat); size = sizeof(*cmd) + sizeof(*pkg) + cmd_length + buf_size; cmd = calloc(1, size); if (!cmd) return NULL; cmd->bus = bus; ndctl_cmd_ref(cmd); cmd->type = ND_CMD_CALL; cmd->get_firmware_status = bus_get_firmware_status; cmd->size = size; cmd->status = 1; pkg = (struct nd_cmd_pkg *)&cmd->cmd_buf[0]; pkg->nd_command = NFIT_CMD_ARS_INJECT_GET; pkg->nd_size_in = 0; pkg->nd_size_out = cmd_length + buf_size; pkg->nd_fw_size = pkg->nd_size_out; return cmd; } ndctl-81/ndctl/lib/papr.c000066400000000000000000000211641476737544500154220ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-2.1 /* * libndctl support for PAPR-SCM based NVDIMMs * * (C) Copyright IBM 2020 * */ #include #include #include #include #include #include #include "private.h" #include "papr.h" /* Utility logging maros for simplify logging */ #define papr_dbg(_dimm, _format_str, ...) dbg(_dimm->bus->ctx, \ "%s:" _format_str, \ ndctl_dimm_get_devname(_dimm), \ ##__VA_ARGS__) #define papr_err(_dimm, _format_str, ...) err(_dimm->bus->ctx, \ "%s:" _format_str, \ ndctl_dimm_get_devname(_dimm), \ ##__VA_ARGS__) /* Convert a ndctl_cmd to pdsm package */ #define to_pdsm(C) (&(C)->papr[0].pdsm) /* Convert a ndctl_cmd to nd_cmd_pkg */ #define to_ndcmd(C) (&(C)->papr[0].gen) /* Return payload from a ndctl_cmd */ #define to_payload(C) (&(C)->papr[0].pdsm.payload) /* return the pdsm command */ #define to_pdsm_cmd(C) ((enum papr_pdsm)to_ndcmd(C)->nd_command) static bool papr_cmd_is_supported(struct ndctl_dimm *dimm, int cmd) { /* Handle this separately to support monitor mode */ if (cmd == ND_CMD_SMART) return true; return !!(dimm->cmd_mask & (1ULL << cmd)); } static u32 papr_get_firmware_status(struct ndctl_cmd *cmd) { const struct nd_pkg_pdsm *pcmd = to_pdsm(cmd); return (u32) pcmd->cmd_status; } static int papr_xlat_firmware_status(struct ndctl_cmd *cmd) { return (cmd->type == ND_CMD_CALL) ? to_pdsm(cmd)->cmd_status : 0; } /* Verify if the given command is supported and valid */ static bool cmd_is_valid(struct ndctl_cmd *cmd) { const struct nd_cmd_pkg *ncmd = NULL; if (cmd == NULL) return false; ncmd = to_ndcmd(cmd); /* Verify the command family */ if (ncmd->nd_family != NVDIMM_FAMILY_PAPR) { papr_err(cmd->dimm, "Invalid command family:0x%016llx\n", ncmd->nd_family); return false; } /* Verify the PDSM */ if (ncmd->nd_command <= PAPR_PDSM_MIN || ncmd->nd_command >= PAPR_PDSM_MAX) { papr_err(cmd->dimm, "Invalid command :0x%016llx\n", ncmd->nd_command); return false; } return true; } /* Allocate a struct ndctl_cmd for given pdsm request with payload size */ static struct ndctl_cmd *allocate_cmd(struct ndctl_dimm *dimm, enum papr_pdsm pdsm_cmd, size_t payload_size) { struct ndctl_cmd *cmd; /* Verify that payload size is within acceptable range */ if (payload_size > ND_PDSM_PAYLOAD_MAX_SIZE) { papr_err(dimm, "Requested payload size too large %lu bytes\n", payload_size); return NULL; } cmd = calloc(1, sizeof(struct ndctl_cmd) + sizeof(struct nd_pkg_papr)); if (!cmd) return NULL; ndctl_cmd_ref(cmd); cmd->dimm = dimm; cmd->type = ND_CMD_CALL; cmd->status = 0; cmd->get_firmware_status = &papr_get_firmware_status; /* Populate the nd_cmd_pkg contained in nd_pkg_pdsm */ *to_ndcmd(cmd) = (struct nd_cmd_pkg) { .nd_family = NVDIMM_FAMILY_PAPR, .nd_command = pdsm_cmd, .nd_size_in = 0, .nd_size_out = ND_PDSM_HDR_SIZE + payload_size, .nd_fw_size = 0, }; return cmd; } /* Parse the nd_papr_pdsm_health and update dimm flags */ static int update_dimm_flags(struct ndctl_dimm *dimm, struct nd_papr_pdsm_health *health) { /* Update the dimm flags */ dimm->flags.f_arm = health->dimm_unarmed; dimm->flags.f_flush = health->dimm_bad_shutdown; dimm->flags.f_restore = health->dimm_bad_restore; dimm->flags.f_smart = (health->dimm_health != 0); return 0; } /* Validate the ndctl_cmd and return applicable flags */ static unsigned int papr_smart_get_flags(struct ndctl_cmd *cmd) { struct nd_pkg_pdsm *pcmd; struct nd_papr_pdsm_health health; unsigned int flags; if (!cmd_is_valid(cmd)) return 0; pcmd = to_pdsm(cmd); /* If error reported then return empty flags */ if (pcmd->cmd_status) { papr_err(cmd->dimm, "PDSM(0x%x) reported error:%d\n", to_pdsm_cmd(cmd), pcmd->cmd_status); return 0; } /* * In case of nvdimm health PDSM, update dimm flags * and return possible flags. */ if (to_pdsm_cmd(cmd) == PAPR_PDSM_HEALTH) { health = pcmd->payload.health; update_dimm_flags(cmd->dimm, &health); flags = ND_SMART_HEALTH_VALID | ND_SMART_SHUTDOWN_VALID; /* check for extension flags */ if (health.extension_flags & PDSM_DIMM_HEALTH_RUN_GAUGE_VALID) flags |= ND_SMART_USED_VALID; if (health.extension_flags & PDSM_DIMM_DSC_VALID) flags |= ND_SMART_SHUTDOWN_COUNT_VALID; return flags; } /* Else return empty flags */ return 0; } static struct ndctl_cmd *papr_new_smart_health(struct ndctl_dimm *dimm) { struct ndctl_cmd *cmd; cmd = allocate_cmd(dimm, PAPR_PDSM_HEALTH, sizeof(struct nd_papr_pdsm_health)); if (!cmd) papr_err(dimm, "Unable to allocate smart_health command\n"); return cmd; } static unsigned int papr_smart_get_health(struct ndctl_cmd *cmd) { struct nd_papr_pdsm_health health; /* Ignore in case of error or invalid pdsm */ if (!cmd_is_valid(cmd) || to_pdsm(cmd)->cmd_status != 0 || to_pdsm_cmd(cmd) != PAPR_PDSM_HEALTH) return 0; /* get the payload from command */ health = to_payload(cmd)->health; /* Use some math to return one of defined ND_SMART_*_HEALTH values */ return !health.dimm_health ? 0 : 1 << (health.dimm_health - 1); } static unsigned int papr_smart_get_shutdown_state(struct ndctl_cmd *cmd) { struct nd_papr_pdsm_health health; /* Ignore in case of error or invalid pdsm */ if (!cmd_is_valid(cmd) || to_pdsm(cmd)->cmd_status != 0 || to_pdsm_cmd(cmd) != PAPR_PDSM_HEALTH) return 0; /* get the payload from command */ health = to_payload(cmd)->health; /* return the bad shutdown flag returned from papr_scm */ return health.dimm_bad_shutdown; } static int papr_smart_inject_supported(struct ndctl_dimm *dimm) { if (!ndctl_dimm_is_cmd_supported(dimm, ND_CMD_CALL)) return -EOPNOTSUPP; if (!test_dimm_dsm(dimm, PAPR_PDSM_SMART_INJECT)) return -EIO; return ND_SMART_INJECT_HEALTH_STATE | ND_SMART_INJECT_UNCLEAN_SHUTDOWN; } static int papr_smart_inject_valid(struct ndctl_cmd *cmd) { if (cmd->type != ND_CMD_CALL || to_pdsm(cmd)->cmd_status != 0 || to_pdsm_cmd(cmd) != PAPR_PDSM_SMART_INJECT) return -EINVAL; return 0; } static struct ndctl_cmd *papr_new_smart_inject(struct ndctl_dimm *dimm) { struct ndctl_cmd *cmd; cmd = allocate_cmd(dimm, PAPR_PDSM_SMART_INJECT, sizeof(struct nd_papr_pdsm_smart_inject)); if (!cmd) return NULL; /* Set the input payload size */ to_ndcmd(cmd)->nd_size_in = ND_PDSM_HDR_SIZE + sizeof(struct nd_papr_pdsm_smart_inject); return cmd; } static unsigned int papr_smart_get_life_used(struct ndctl_cmd *cmd) { struct nd_papr_pdsm_health health; /* Ignore in case of error or invalid pdsm */ if (!cmd_is_valid(cmd) || to_pdsm(cmd)->cmd_status != 0 || to_pdsm_cmd(cmd) != PAPR_PDSM_HEALTH) return 0; /* get the payload from command */ health = to_payload(cmd)->health; /* return dimm life remaining from the health payload */ return (health.extension_flags & PDSM_DIMM_HEALTH_RUN_GAUGE_VALID) ? (100 - health.dimm_fuel_gauge) : 0; } static unsigned int papr_smart_get_shutdown_count(struct ndctl_cmd *cmd) { struct nd_papr_pdsm_health health; /* Ignore in case of error or invalid pdsm */ if (!cmd_is_valid(cmd) || to_pdsm(cmd)->cmd_status != 0 || to_pdsm_cmd(cmd) != PAPR_PDSM_HEALTH) return 0; /* get the payload from command */ health = to_payload(cmd)->health; return (health.extension_flags & PDSM_DIMM_DSC_VALID) ? (health.dimm_dsc) : 0; } static int papr_cmd_smart_inject_fatal(struct ndctl_cmd *cmd, bool enable) { if (papr_smart_inject_valid(cmd) < 0) return -EINVAL; to_payload(cmd)->inject.flags |= PDSM_SMART_INJECT_HEALTH_FATAL; to_payload(cmd)->inject.fatal_enable = enable; return 0; } static int papr_cmd_smart_inject_unsafe_shutdown(struct ndctl_cmd *cmd, bool enable) { if (papr_smart_inject_valid(cmd) < 0) return -EINVAL; to_payload(cmd)->inject.flags |= PDSM_SMART_INJECT_BAD_SHUTDOWN; to_payload(cmd)->inject.unsafe_shutdown_enable = enable; return 0; } struct ndctl_dimm_ops * const papr_dimm_ops = &(struct ndctl_dimm_ops) { .cmd_is_supported = papr_cmd_is_supported, .new_smart_inject = papr_new_smart_inject, .smart_inject_supported = papr_smart_inject_supported, .smart_inject_fatal = papr_cmd_smart_inject_fatal, .smart_inject_unsafe_shutdown = papr_cmd_smart_inject_unsafe_shutdown, .smart_get_flags = papr_smart_get_flags, .get_firmware_status = papr_get_firmware_status, .xlat_firmware_status = papr_xlat_firmware_status, .new_smart = papr_new_smart_health, .smart_get_health = papr_smart_get_health, .smart_get_shutdown_state = papr_smart_get_shutdown_state, .smart_get_life_used = papr_smart_get_life_used, .smart_get_shutdown_count = papr_smart_get_shutdown_count, }; ndctl-81/ndctl/lib/papr.h000066400000000000000000000004351476737544500154250ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1 */ /* (C) Copyright IBM 2020 */ #ifndef __PAPR_H__ #define __PAPR_H__ #include /* Wraps a nd_cmd generic header with pdsm header */ struct nd_pkg_papr { struct nd_cmd_pkg gen; struct nd_pkg_pdsm pdsm; }; #endif /* __PAPR_H__ */ ndctl-81/ndctl/lib/papr_pdsm.h000066400000000000000000000130671476737544500164550ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ /* * PAPR nvDimm Specific Methods (PDSM) and structs for libndctl * * (C) Copyright IBM 2020 * * Author: Vaibhav Jain */ #ifndef _UAPI_ASM_POWERPC_PAPR_PDSM_H_ #define _UAPI_ASM_POWERPC_PAPR_PDSM_H_ #include #include /* * PDSM Envelope: * * The ioctl ND_CMD_CALL exchange data between user-space and kernel via * envelope which consists of 2 headers sections and payload sections as * illustrated below: * +-----------------+---------------+---------------------------+ * | 64-Bytes | 8-Bytes | Max 184-Bytes | * +-----------------+---------------+---------------------------+ * | ND-HEADER | PDSM-HEADER | PDSM-PAYLOAD | * +-----------------+---------------+---------------------------+ * | nd_family | | | * | nd_size_out | cmd_status | | * | nd_size_in | reserved | nd_pdsm_payload | * | nd_command | payload --> | | * | nd_fw_size | | | * | nd_payload ---> | | | * +---------------+-----------------+---------------------------+ * * ND Header: * This is the generic libnvdimm header described as 'struct nd_cmd_pkg' * which is interpreted by libnvdimm before passed on to papr_scm. Important * member fields used are: * 'nd_family' : (In) NVDIMM_FAMILY_PAPR_SCM * 'nd_size_in' : (In) PDSM-HEADER + PDSM-IN-PAYLOAD (usually 0) * 'nd_size_out' : (In) PDSM-HEADER + PDSM-RETURN-PAYLOAD * 'nd_command' : (In) One of PAPR_PDSM_XXX * 'nd_fw_size' : (Out) PDSM-HEADER + size of actual payload returned * * PDSM Header: * This is papr-scm specific header that precedes the payload. This is defined * as nd_cmd_pdsm_pkg. Following fields aare available in this header: * * 'cmd_status' : (Out) Errors if any encountered while servicing PDSM. * 'reserved' : Not used, reserved for future and should be set to 0. * 'payload' : A union of all the possible payload structs * * PDSM Payload: * * The layout of the PDSM Payload is defined by various structs shared between * papr_scm and libndctl so that contents of payload can be interpreted. As such * its defined as a union of all possible payload structs as * 'union nd_pdsm_payload'. Based on the value of 'nd_cmd_pkg.nd_command' * appropriate member of the union is accessed. */ /* Max payload size that we can handle */ #define ND_PDSM_PAYLOAD_MAX_SIZE 184 /* Max payload size that we can handle */ #define ND_PDSM_HDR_SIZE \ (sizeof(struct nd_pkg_pdsm) - ND_PDSM_PAYLOAD_MAX_SIZE) /* Various nvdimm health indicators */ #define PAPR_PDSM_DIMM_HEALTHY 0 #define PAPR_PDSM_DIMM_UNHEALTHY 1 #define PAPR_PDSM_DIMM_CRITICAL 2 #define PAPR_PDSM_DIMM_FATAL 3 /* Indicate that the 'dimm_fuel_gauge' field is valid */ #define PDSM_DIMM_HEALTH_RUN_GAUGE_VALID 1 /* Indicate that the 'dimm_dsc' field is valid */ #define PDSM_DIMM_DSC_VALID 2 /* * Struct exchanged between kernel & ndctl in for PAPR_PDSM_HEALTH * Various flags indicate the health status of the dimm. * * extension_flags : Any extension fields present in the struct. * dimm_unarmed : Dimm not armed. So contents wont persist. * dimm_bad_shutdown : Previous shutdown did not persist contents. * dimm_bad_restore : Contents from previous shutdown werent restored. * dimm_scrubbed : Contents of the dimm have been scrubbed. * dimm_locked : Contents of the dimm cant be modified until CEC reboot * dimm_encrypted : Contents of dimm are encrypted. * dimm_health : Dimm health indicator. One of PAPR_PDSM_DIMM_XXXX * dimm_fuel_gauge : Life remaining of DIMM as a percentage from 0-100 */ struct nd_papr_pdsm_health { union { struct { __u32 extension_flags; __u8 dimm_unarmed; __u8 dimm_bad_shutdown; __u8 dimm_bad_restore; __u8 dimm_scrubbed; __u8 dimm_locked; __u8 dimm_encrypted; __u16 dimm_health; /* Extension flag PDSM_DIMM_HEALTH_RUN_GAUGE_VALID */ __u16 dimm_fuel_gauge; /* Extension flag PDSM_DIMM_DSC_VALID */ __u64 dimm_dsc; }; __u8 buf[ND_PDSM_PAYLOAD_MAX_SIZE]; }; }; /* * Methods to be embedded in ND_CMD_CALL request. These are sent to the kernel * via 'nd_cmd_pkg.nd_command' member of the ioctl struct */ enum papr_pdsm { PAPR_PDSM_MIN = 0x0, PAPR_PDSM_HEALTH, PAPR_PDSM_SMART_INJECT, PAPR_PDSM_MAX, }; /* Flags for injecting specific smart errors */ #define PDSM_SMART_INJECT_HEALTH_FATAL (1 << 0) #define PDSM_SMART_INJECT_BAD_SHUTDOWN (1 << 1) struct nd_papr_pdsm_smart_inject { union { struct { /* One or more of PDSM_SMART_INJECT_ */ __u32 flags; __u8 fatal_enable; __u8 unsafe_shutdown_enable; }; __u8 buf[ND_PDSM_PAYLOAD_MAX_SIZE]; }; }; /* Maximal union that can hold all possible payload types */ union nd_pdsm_payload { struct nd_papr_pdsm_health health; struct nd_papr_pdsm_smart_inject inject; __u8 buf[ND_PDSM_PAYLOAD_MAX_SIZE]; } __attribute__((packed)); /* * PDSM-header + payload expected with ND_CMD_CALL ioctl from libnvdimm * Valid member of union 'payload' is identified via 'nd_cmd_pkg.nd_command' * that should always precede this struct when sent to papr_scm via CMD_CALL * interface. */ struct nd_pkg_pdsm { __s32 cmd_status; /* Out: Sub-cmd status returned back */ __u16 reserved[2]; /* Ignored and to be set as '0' */ union nd_pdsm_payload payload; } __attribute__((packed)); #endif /* _UAPI_ASM_POWERPC_PAPR_PDSM_H_ */ ndctl-81/ndctl/lib/private.h000066400000000000000000000301221476737544500161310ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1 */ /* Copyright (C) 2014-2020, Intel Corporation. All rights reserved. */ #ifndef _LIBNDCTL_PRIVATE_H_ #define _LIBNDCTL_PRIVATE_H_ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "intel.h" #include "hpe1.h" #include "msft.h" #include "hyperv.h" #include "papr.h" struct nvdimm_data { struct ndctl_cmd *cmd_read; void *data; unsigned long config_size; size_t nslabel_size; int ns_current, ns_next; }; /** * struct ndctl_dimm - memory device as identified by NFIT * @module: kernel module (libnvdimm) * @handle: NFIT-handle value * @major: /dev/nmemX major character device number * @minor: /dev/nmemX minor character device number * @phys_id: SMBIOS physical id * @vendor_id: hardware component vendor * @device_id: hardware device id * @revision_id: hardware revision id * @node: system node-id * @socket: socket-id in the node * @imc: memory-controller-id in the socket * @channel: channel-id in the memory-controller * @dimm: dimm-id in the channel * @formats: number of support interfaces * @format: array of format interface code numbers */ struct ndctl_dimm { struct kmod_module *module; struct ndctl_bus *bus; struct ndctl_dimm_ops *ops; struct nvdimm_data ndd; unsigned int handle, major, minor, serial; unsigned short phys_id; unsigned short vendor_id; unsigned short device_id; unsigned short revision_id; unsigned short subsystem_vendor_id; unsigned short subsystem_device_id; unsigned short subsystem_revision_id; unsigned short manufacturing_date; unsigned char manufacturing_location; unsigned long cmd_family; unsigned long cmd_mask; unsigned long nfit_dsm_mask; long long dirty_shutdown; enum ndctl_fwa_state fwa_state; enum ndctl_fwa_result fwa_result; char *unique_id; char *dimm_path; char *dimm_buf; char *bus_prefix; int health_eventfd; int buf_len; int id; union dimm_flags { unsigned long flags; struct { unsigned int f_map:1; unsigned int f_arm:1; unsigned int f_save:1; unsigned int f_flush:1; unsigned int f_smart:1; unsigned int f_restore:1; unsigned int f_notify:1; }; } flags; int locked; int aliased; struct list_node list; int formats; int format[0]; }; enum dsm_support { DIMM_DSM_UNSUPPORTED, /* don't attempt command */ DIMM_DSM_SUPPORTED, /* good to go */ DIMM_DSM_UNKNOWN, /* try ND_CMD_CALL on older kernels */ }; static inline enum dsm_support test_dimm_dsm(struct ndctl_dimm *dimm, int fn) { if (dimm->nfit_dsm_mask == ULONG_MAX) { return DIMM_DSM_UNKNOWN; } else if (dimm->nfit_dsm_mask & (1 << fn)) return DIMM_DSM_SUPPORTED; return DIMM_DSM_UNSUPPORTED; } void region_flag_refresh(struct ndctl_region *region); /** * struct ndctl_ctx - library user context to find "nd" instances * * Instantiate with ndctl_new(), which takes an initial reference. Free * the context by dropping the reference count to zero with * ndctrl_unref(), or take additional references with ndctl_ref() * @timeout: default library timeout in milliseconds */ struct ndctl_ctx { /* log_ctx must be first member for ndctl_set_log_fn compat */ struct log_ctx ctx; int refcount; int regions_init; void *userdata; struct list_head busses; const char *config_path; int busses_init; struct udev *udev; struct udev_queue *udev_queue; struct kmod_ctx *kmod_ctx; struct daxctl_ctx *daxctl_ctx; unsigned long timeout; void *private_data; }; /** * struct ndctl_bus - a nfit table instance * @major: control character device major number * @minor: control character device minor number * @revision: NFIT table revision number * @provider: identifier for the source of the NFIT table * * The expectation is one NFIT/nd bus per system provided by platform * firmware (for example @provider == "ACPI.NFIT"). However, the * nfit_test module provides multiple test busses with provider names of * the format "nfit_test.N" */ struct ndctl_bus { struct ndctl_ctx *ctx; unsigned int id, major, minor, revision; char *provider; struct list_head dimms; struct list_head regions; struct list_node list; int dimms_init; int regions_init; int has_nfit; int has_of_node; int has_cxl; char *bus_path; char *bus_buf; size_t buf_len; char *wait_probe_path; char *scrub_path; unsigned long cmd_mask; unsigned long nfit_dsm_mask; enum ndctl_fwa_state fwa_state; enum ndctl_fwa_method fwa_method; }; /** * struct ndctl_lbasize - lbasize info for btt and blk-namespace devices * @select: currently selected sector_size * @supported: possible sector_size options * @num: number of entries in @supported */ struct ndctl_lbasize { int select; unsigned int *supported; int num; }; struct badblocks_iter { struct badblock bb; FILE *file; }; /** * struct ndctl_namespace - device claimed by the nd_blk or nd_pmem driver * @module: kernel module * @type: integer nd-bus device-type * @type_name: 'namespace_io', 'namespace_pmem', or 'namespace_block' * @namespace_path: devpath for namespace device * @bdev: associated block_device of a namespace * @size: unsigned * @numa_node: numa node attribute * @target_node: target node were this region to be onlined * * A 'namespace' is the resulting device after region-aliasing and * label-parsing is resolved. */ struct ndctl_namespace { struct kmod_module *module; struct ndctl_region *region; struct list_node list; char *ndns_path; char *ndns_buf; char *bdev; int type, id, buf_len, raw_mode; int generation; unsigned long long resource, size; enum ndctl_namespace_mode enforce_mode; struct badblocks_iter bb_iter; char *alt_name; uuid_t uuid; struct ndctl_lbasize lbasize; int numa_node, target_node; struct list_head injected_bb; }; /** * struct ndctl_cmd - device-specific-method (_DSM ioctl) container * @dimm: set if the command is relative to a dimm, NULL otherwise * @bus: set if the command is relative to a bus (like ARS), NULL otherwise * @refcount: reference for passing command buffer around * @type: cmd number * @size: total size of the ndctl_cmd allocation * @status: negative if failed, 0 if success, > 0 if never submitted * @get_firmware_status: per command firmware status field retrieval * @iter: iterator for multi-xfer commands * @source: source cmd of an inherited iter.total_buf * * For dynamically sized commands like 'get_config', 'set_config', or * 'vendor', @size encompasses the entire buffer for the command input * and response output data. * * A command may only specify one of @source, or @iter.total_buf, not both. */ struct ndctl_cmd { struct ndctl_dimm *dimm; struct ndctl_bus *bus; int refcount; int type; int size; int status; u32 (*get_firmware_status)(struct ndctl_cmd *cmd); u32 (*get_xfer)(struct ndctl_cmd *cmd); u32 (*get_offset)(struct ndctl_cmd *cmd); void (*set_xfer)(struct ndctl_cmd *cmd, u32 xfer); void (*set_offset)(struct ndctl_cmd *cmd, u32 offset); struct ndctl_cmd_iter { u32 init_offset; u8 *data; /* pointer to the data buffer location in cmd */ u32 max_xfer; char *total_buf; u32 total_xfer; int dir; } iter; struct ndctl_cmd *source; union { struct nd_cmd_ars_cap ars_cap[0]; struct nd_cmd_ars_start ars_start[0]; struct nd_cmd_ars_status ars_status[0]; struct nd_cmd_clear_error clear_err[0]; struct nd_cmd_pkg pkg[0]; struct nd_cmd_bus cmd_bus[0]; struct ndn_pkg_hpe1 hpe1[0]; struct ndn_pkg_msft msft[0]; struct nd_pkg_hyperv hyperv[0]; struct nd_pkg_intel intel[0]; struct nd_pkg_papr papr[0]; struct nd_cmd_get_config_size get_size[0]; struct nd_cmd_get_config_data_hdr get_data[0]; struct nd_cmd_set_config_hdr set_data[0]; struct nd_cmd_vendor_hdr vendor[0]; char cmd_buf[0]; }; }; struct ndctl_bb { u64 block; u64 count; struct list_node list; }; /* ars_status flags */ #define ND_ARS_STAT_FLAG_OVERFLOW (1 << 0) struct ndctl_dimm_ops { const char *(*cmd_desc)(int); bool (*cmd_is_supported)(struct ndctl_dimm *, int); struct ndctl_cmd *(*new_smart)(struct ndctl_dimm *); unsigned int (*smart_get_flags)(struct ndctl_cmd *); unsigned int (*smart_get_health)(struct ndctl_cmd *); unsigned int (*smart_get_media_temperature)(struct ndctl_cmd *); unsigned int (*smart_get_ctrl_temperature)(struct ndctl_cmd *); unsigned int (*smart_get_spares)(struct ndctl_cmd *); unsigned int (*smart_get_alarm_flags)(struct ndctl_cmd *); unsigned int (*smart_get_life_used)(struct ndctl_cmd *); unsigned int (*smart_get_shutdown_state)(struct ndctl_cmd *); unsigned int (*smart_get_shutdown_count)(struct ndctl_cmd *); unsigned int (*smart_get_vendor_size)(struct ndctl_cmd *); unsigned char *(*smart_get_vendor_data)(struct ndctl_cmd *); struct ndctl_cmd *(*new_smart_threshold)(struct ndctl_dimm *); unsigned int (*smart_threshold_get_alarm_control)(struct ndctl_cmd *); unsigned int (*smart_threshold_get_media_temperature)(struct ndctl_cmd *); unsigned int (*smart_threshold_get_ctrl_temperature)(struct ndctl_cmd *); unsigned int (*smart_threshold_get_spares)(struct ndctl_cmd *); struct ndctl_cmd *(*new_smart_set_threshold)(struct ndctl_cmd *); unsigned int (*smart_threshold_get_supported_alarms)(struct ndctl_cmd *); int (*smart_threshold_set_alarm_control)(struct ndctl_cmd *, unsigned int); int (*smart_threshold_set_media_temperature)(struct ndctl_cmd *, unsigned int); int (*smart_threshold_set_ctrl_temperature)(struct ndctl_cmd *, unsigned int); int (*smart_threshold_set_spares)(struct ndctl_cmd *, unsigned int); struct ndctl_cmd *(*new_smart_inject)(struct ndctl_dimm *); int (*smart_inject_media_temperature)(struct ndctl_cmd *, bool, unsigned int); int (*smart_inject_ctrl_temperature)(struct ndctl_cmd *, bool, unsigned int); int (*smart_inject_spares)(struct ndctl_cmd *, bool, unsigned int); int (*smart_inject_fatal)(struct ndctl_cmd *, bool); int (*smart_inject_unsafe_shutdown)(struct ndctl_cmd *, bool); int (*smart_inject_supported)(struct ndctl_dimm *); struct ndctl_cmd *(*new_fw_get_info)(struct ndctl_dimm *); unsigned int (*fw_info_get_storage_size)(struct ndctl_cmd *); unsigned int (*fw_info_get_max_send_len)(struct ndctl_cmd *); unsigned int (*fw_info_get_query_interval)(struct ndctl_cmd *); unsigned int (*fw_info_get_max_query_time)(struct ndctl_cmd *); unsigned long long (*fw_info_get_run_version)(struct ndctl_cmd *); unsigned long long (*fw_info_get_updated_version)(struct ndctl_cmd *); struct ndctl_cmd *(*new_fw_start_update)(struct ndctl_dimm *); unsigned int (*fw_start_get_context)(struct ndctl_cmd *); struct ndctl_cmd *(*new_fw_send)(struct ndctl_cmd *, unsigned int, unsigned int, void *); struct ndctl_cmd *(*new_fw_finish)(struct ndctl_cmd *); struct ndctl_cmd *(*new_fw_abort)(struct ndctl_cmd *); struct ndctl_cmd *(*new_fw_finish_query)(struct ndctl_cmd *); unsigned long long (*fw_fquery_get_fw_rev)(struct ndctl_cmd *); enum ND_FW_STATUS (*fw_xlat_firmware_status)(struct ndctl_cmd *); struct ndctl_cmd *(*new_ack_shutdown_count)(struct ndctl_dimm *); int (*fw_update_supported)(struct ndctl_dimm *); int (*xlat_firmware_status)(struct ndctl_cmd *); u32 (*get_firmware_status)(struct ndctl_cmd *); }; extern struct ndctl_dimm_ops * const intel_dimm_ops; extern struct ndctl_dimm_ops * const hpe1_dimm_ops; extern struct ndctl_dimm_ops * const msft_dimm_ops; extern struct ndctl_dimm_ops * const hyperv_dimm_ops; extern struct ndctl_dimm_ops * const papr_dimm_ops; static inline struct ndctl_bus *cmd_to_bus(struct ndctl_cmd *cmd) { if (cmd->dimm) return ndctl_dimm_get_bus(cmd->dimm); return cmd->bus; } #define NDCTL_EXPORT __attribute__ ((visibility("default"))) static inline int check_udev(struct udev *udev) { return udev ? 0 : -ENXIO; } static inline int check_kmod(struct kmod_ctx *kmod_ctx) { return kmod_ctx ? 0 : -ENXIO; } struct ndctl_cmd *ndctl_bus_cmd_new_err_inj(struct ndctl_bus *bus); struct ndctl_cmd *ndctl_bus_cmd_new_err_inj_clr(struct ndctl_bus *bus); struct ndctl_cmd *ndctl_bus_cmd_new_err_inj_stat(struct ndctl_bus *bus, u32 buf_size); #endif /* _LIBNDCTL_PRIVATE_H_ */ ndctl-81/ndctl/lib/smart.c000066400000000000000000000112631476737544500156050ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-2.1 // Copyright (C) 2016-2020, Intel Corporation. All rights reserved. #include #include #include #include #include "private.h" /* * Define the wrappers around the ndctl_dimm_ops: */ NDCTL_EXPORT struct ndctl_cmd *ndctl_dimm_cmd_new_smart( struct ndctl_dimm *dimm) { struct ndctl_dimm_ops *ops = dimm->ops; if (ops && ops->new_smart) return ops->new_smart(dimm); else return NULL; } NDCTL_EXPORT struct ndctl_cmd *ndctl_dimm_cmd_new_smart_threshold( struct ndctl_dimm *dimm) { struct ndctl_dimm_ops *ops = dimm->ops; if (ops && ops->new_smart_threshold) return ops->new_smart_threshold(dimm); else return NULL; } /* * smart_set_threshold is a read-modify-write command it depends on a * successfully completed smart_threshold command for its defaults. */ NDCTL_EXPORT struct ndctl_cmd *ndctl_dimm_cmd_new_smart_set_threshold( struct ndctl_cmd *cmd) { struct ndctl_dimm_ops *ops; if (!cmd || !cmd->dimm) return NULL; ops = cmd->dimm->ops; if (ops && ops->new_smart_set_threshold) return ops->new_smart_set_threshold(cmd); else return NULL; } #define smart_cmd_op(op, rettype, defretvalue) \ NDCTL_EXPORT rettype ndctl_cmd_##op(struct ndctl_cmd *cmd) \ { \ if (cmd->dimm) { \ struct ndctl_dimm_ops *ops = cmd->dimm->ops; \ if (ops && ops->op) \ return ops->op(cmd); \ } \ return defretvalue; \ } smart_cmd_op(smart_get_flags, unsigned int, 0) smart_cmd_op(smart_get_health, unsigned int, 0) smart_cmd_op(smart_get_media_temperature, unsigned int, 0) smart_cmd_op(smart_get_ctrl_temperature, unsigned int, 0) smart_cmd_op(smart_get_spares, unsigned int, 0) smart_cmd_op(smart_get_alarm_flags, unsigned int, 0) smart_cmd_op(smart_get_life_used, unsigned int, 0) smart_cmd_op(smart_get_shutdown_state, unsigned int, 0) smart_cmd_op(smart_get_shutdown_count, unsigned int, 0) smart_cmd_op(smart_get_vendor_size, unsigned int, 0) smart_cmd_op(smart_get_vendor_data, unsigned char *, NULL) smart_cmd_op(smart_threshold_get_alarm_control, unsigned int, 0) smart_cmd_op(smart_threshold_get_media_temperature, unsigned int, 0) smart_cmd_op(smart_threshold_get_ctrl_temperature, unsigned int, 0) smart_cmd_op(smart_threshold_get_spares, unsigned int, 0) NDCTL_EXPORT unsigned int ndctl_cmd_smart_get_temperature(struct ndctl_cmd *cmd) { return ndctl_cmd_smart_get_media_temperature(cmd); } NDCTL_EXPORT unsigned int ndctl_cmd_smart_threshold_get_temperature( struct ndctl_cmd *cmd) { return ndctl_cmd_smart_threshold_get_media_temperature(cmd); } smart_cmd_op(smart_threshold_get_supported_alarms, unsigned int, 0); #define smart_cmd_set_op(op) \ NDCTL_EXPORT int ndctl_cmd_##op(struct ndctl_cmd *cmd, unsigned int val) \ { \ if (cmd->dimm) { \ struct ndctl_dimm_ops *ops = cmd->dimm->ops; \ if (ops && ops->op) \ return ops->op(cmd, val); \ } \ return -ENXIO; \ } smart_cmd_set_op(smart_threshold_set_alarm_control) smart_cmd_set_op(smart_threshold_set_media_temperature) smart_cmd_set_op(smart_threshold_set_ctrl_temperature) smart_cmd_set_op(smart_threshold_set_spares) NDCTL_EXPORT int ndctl_cmd_smart_threshold_set_temperature( struct ndctl_cmd *cmd, unsigned int val) { return ndctl_cmd_smart_threshold_set_media_temperature(cmd, val); } NDCTL_EXPORT struct ndctl_cmd *ndctl_dimm_cmd_new_smart_inject( struct ndctl_dimm *dimm) { struct ndctl_dimm_ops *ops = dimm->ops; if (ops && ops->new_smart_inject) return ops->new_smart_inject(dimm); else return NULL; } #define smart_cmd_inject_val(op) \ NDCTL_EXPORT int ndctl_cmd_##op(struct ndctl_cmd *cmd, bool enable, unsigned int val) \ { \ if (cmd->dimm) { \ struct ndctl_dimm_ops *ops = cmd->dimm->ops; \ if (ops && ops->op) \ return ops->op(cmd, enable, val); \ } \ return -ENXIO; \ } smart_cmd_inject_val(smart_inject_media_temperature) smart_cmd_inject_val(smart_inject_ctrl_temperature) smart_cmd_inject_val(smart_inject_spares) #define smart_cmd_inject(op) \ NDCTL_EXPORT int ndctl_cmd_##op(struct ndctl_cmd *cmd, bool enable) \ { \ if (cmd->dimm) { \ struct ndctl_dimm_ops *ops = cmd->dimm->ops; \ if (ops && ops->op) \ return ops->op(cmd, enable); \ } \ return -ENXIO; \ } smart_cmd_inject(smart_inject_fatal) smart_cmd_inject(smart_inject_unsafe_shutdown) NDCTL_EXPORT struct ndctl_cmd * ndctl_dimm_cmd_new_ack_shutdown_count(struct ndctl_dimm *dimm) { struct ndctl_dimm_ops *ops = dimm->ops; if (ops && ops->new_ack_shutdown_count) return ops->new_ack_shutdown_count(dimm); else return NULL; } NDCTL_EXPORT int ndctl_dimm_smart_inject_supported(struct ndctl_dimm *dimm) { struct ndctl_dimm_ops *ops = dimm->ops; if (ops && ops->smart_inject_supported) return ops->smart_inject_supported(dimm); else return -ENOTTY; } ndctl-81/ndctl/libndctl-nfit.h000066400000000000000000000043161476737544500164500ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1 */ /* Copyright (C) 2017 Hewlett Packard Enterprise Development LP */ /* Copyright (C) 2017-2020 Intel Corporation. All rights reserved. */ #ifndef __LIBNDCTL_NFIT_H__ #define __LIBNDCTL_NFIT_H__ #include #include /* * libndctl-nfit.h : definitions for NFIT related commands/functions. */ /* nfit command numbers which are called via ND_CMD_CALL */ enum { NFIT_CMD_TRANSLATE_SPA = 5, NFIT_CMD_ARS_INJECT_SET = 7, NFIT_CMD_ARS_INJECT_CLEAR = 8, NFIT_CMD_ARS_INJECT_GET = 9, }; /* error number of Translate SPA by firmware */ #define ND_TRANSLATE_SPA_STATUS_INVALID_SPA 2 /* status definitions for error injection */ #define ND_ARS_ERR_INJ_STATUS_NOT_SUPP 1 #define ND_ARS_ERR_INJ_STATUS_INVALID_PARAM 2 enum err_inj_options { ND_ARS_ERR_INJ_OPT_NOTIFY = 0, }; /* * The following structures are command packages which are * defined by ACPI 6.2 (or later). */ /* For Translate SPA */ struct nd_cmd_translate_spa { __u64 spa; __u32 status; __u8 flags; __u8 _reserved[3]; __u64 translate_length; __u32 num_nvdimms; struct nd_nvdimm_device { __u32 nfit_device_handle; __u32 _reserved; __u64 dpa; } __attribute__((packed)) devices[0]; } __attribute__((packed)); /* For ARS Error Inject */ struct nd_cmd_ars_err_inj { __u64 err_inj_spa_range_base; __u64 err_inj_spa_range_length; __u8 err_inj_options; __u32 status; } __attribute__((packed)); /* For ARS Error Inject Clear */ struct nd_cmd_ars_err_inj_clr { __u64 err_inj_clr_spa_range_base; __u64 err_inj_clr_spa_range_length; __u32 status; } __attribute__((packed)); /* For ARS Error Inject Status Query */ struct nd_cmd_ars_err_inj_stat { __u32 status; __u32 inj_err_rec_count; struct nd_error_stat_query_record { __u64 err_inj_stat_spa_range_base; __u64 err_inj_stat_spa_range_length; } __attribute__((packed)) record[0]; } __attribute__((packed)); struct nd_cmd_bus { struct nd_cmd_pkg gen; union { struct nd_cmd_ars_err_inj_stat err_inj_stat; struct nd_cmd_ars_err_inj_clr err_inj_clr; struct nd_cmd_ars_err_inj err_inj; struct nd_cmd_translate_spa xlat_spa; }; }; int ndctl_bus_is_nfit_cmd_supported(struct ndctl_bus *bus, int cmd); #endif /* __LIBNDCTL_NFIT_H__ */ ndctl-81/ndctl/libndctl.h000066400000000000000000001142161476737544500155130ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1 */ /* Copyright (C) 2014-2020, Intel Corporation. All rights reserved. */ #ifndef _LIBNDCTL_H_ #define _LIBNDCTL_H_ #include #include #include #include #include #include #ifdef HAVE_UUID #include #else typedef unsigned char uuid_t[16]; #endif /* * "nd/ndctl" device/object hierarchy and kernel modules * * +-----------+-----------+-----------+------------------+-----------+ * | DEVICE | BUS | REGION | NAMESPACE | BLOCK | * | CLASSES: | PROVIDERS | DEVICES | DEVICES | DEVICES | * +-----------+-----------+-----------+------------------+-----------+ * | MODULES: | nd_core | nd_core | nd_region | nd_pmem | * | | nd_acpi | nd_region | | nd_blk | * | | nfit_test | | | btt | * +-----------v-----------v-----------v------------------v-----------v * +-----+ * | CTX | * +--+--+ +---------+ +--------------+ +-------+ * | +-> REGION0 +---> NAMESPACE0.0 +---> PMEM3 | * +-------+ +--+---+ | +---------+ +--------------+ +-------+ * | DIMM0 <-----+ BUS0 +---> REGION1 +---> NAMESPACE1.0 +---> PMEM2 | * +-------+ +--+---+ | +---------+ +--------------+ +-------+ * | +-> REGION2 +---> NAMESPACE2.0 +---> PMEM1 | * | +---------+ + ------------ + +-------+ * | * +-------+ | +---------+ +--------------+ +-------+ * | DIMM1 <---+ +--+---+ +-> REGION3 +---> NAMESPACE3.0 +---> PMEM0 | * +-------+ +-+ BUS1 +-+ +---------+ +--------------+ +-------+ * | DIMM2 <---+ +--+---+ +-> REGION4 +---> NAMESPACE4.0 +---> ND0 | * +-------+ | + ------- + +--------------+ +-------+ * | * +-------+ | +--------------+ +-------+ * | DIMM3 <---+ | +-> NAMESPACE5.0 +---> ND2 | * +-------+ | +--+---+ +---------+ | +--------------+ +---------------+ * | DIMM4 <-----+ BUS2 +---> REGION5 +---> NAMESPACE5.1 +---> BTT1 | ND1 | * +-------+ | +------+ +---------+ | +--------------+ +---------------+ * | DIMM5 <---+ +-> NAMESPACE5.2 +---> BTT0 | ND0 | * +-------+ +--------------+ +-------+-------+ * * Notes: * 1/ The object ids are not guaranteed to be stable from boot to boot * 2/ While regions and busses are numbered in sequential/bus-discovery * order, the resulting block devices may appear to have random ids. * Use static attributes of the devices/device-path to generate a * persistent name. */ #ifdef __cplusplus extern "C" { #endif #define ND_EVENT_SPARES_REMAINING (1 << 0) #define ND_EVENT_MEDIA_TEMPERATURE (1 << 1) #define ND_EVENT_CTRL_TEMPERATURE (1 << 2) #define ND_EVENT_HEALTH_STATE (1 << 3) #define ND_EVENT_UNCLEAN_SHUTDOWN (1 << 4) /* Flags indicating support for various smart injection types */ #define ND_SMART_INJECT_SPARES_REMAINING (1 << 0) #define ND_SMART_INJECT_MEDIA_TEMPERATURE (1 << 1) #define ND_SMART_INJECT_CTRL_TEMPERATURE (1 << 2) #define ND_SMART_INJECT_HEALTH_STATE (1 << 3) #define ND_SMART_INJECT_UNCLEAN_SHUTDOWN (1 << 4) size_t ndctl_min_namespace_size(void); size_t ndctl_sizeof_namespace_index(void); size_t ndctl_sizeof_namespace_label(void); double ndctl_decode_smart_temperature(unsigned int temp); unsigned int ndctl_encode_smart_temperature(double temp); struct ndctl_ctx; struct ndctl_ctx *ndctl_ref(struct ndctl_ctx *ctx); struct ndctl_ctx *ndctl_unref(struct ndctl_ctx *ctx); int ndctl_new(struct ndctl_ctx **ctx); void ndctl_set_private_data(struct ndctl_ctx *ctx, void *data); void *ndctl_get_private_data(struct ndctl_ctx *ctx); struct daxctl_ctx; struct daxctl_ctx *ndctl_get_daxctl_ctx(struct ndctl_ctx *ctx); void ndctl_invalidate(struct ndctl_ctx *ctx); void ndctl_set_log_fn(struct ndctl_ctx *ctx, void (*log_fn)(struct ndctl_ctx *ctx, int priority, const char *file, int line, const char *fn, const char *format, va_list args)); int ndctl_get_log_priority(struct ndctl_ctx *ctx); void ndctl_set_log_priority(struct ndctl_ctx *ctx, int priority); void ndctl_set_userdata(struct ndctl_ctx *ctx, void *userdata); void *ndctl_get_userdata(struct ndctl_ctx *ctx); int ndctl_set_config_path(struct ndctl_ctx *ctx, char *config_path); const char *ndctl_get_config_path(struct ndctl_ctx *ctx); enum ndctl_persistence_domain { PERSISTENCE_NONE = 0, PERSISTENCE_MEM_CTRL = 10, PERSISTENCE_CPU_CACHE = 20, PERSISTENCE_UNKNOWN = INT_MAX, }; enum ndctl_fwa_state { NDCTL_FWA_INVALID, NDCTL_FWA_IDLE, NDCTL_FWA_ARMED, NDCTL_FWA_BUSY, NDCTL_FWA_ARM_OVERFLOW, }; enum ndctl_fwa_method { NDCTL_FWA_METHOD_RESET, NDCTL_FWA_METHOD_SUSPEND, NDCTL_FWA_METHOD_LIVE, }; struct ndctl_bus; struct ndctl_bus *ndctl_bus_get_first(struct ndctl_ctx *ctx); struct ndctl_bus *ndctl_bus_get_next(struct ndctl_bus *bus); #define ndctl_bus_foreach(ctx, bus) \ for (bus = ndctl_bus_get_first(ctx); \ bus != NULL; \ bus = ndctl_bus_get_next(bus)) struct ndctl_ctx *ndctl_bus_get_ctx(struct ndctl_bus *bus); int ndctl_bus_has_nfit(struct ndctl_bus *bus); int ndctl_bus_has_of_node(struct ndctl_bus *bus); int ndctl_bus_has_cxl(struct ndctl_bus *bus); int ndctl_bus_is_papr_scm(struct ndctl_bus *bus); unsigned int ndctl_bus_get_major(struct ndctl_bus *bus); unsigned int ndctl_bus_get_minor(struct ndctl_bus *bus); const char *ndctl_bus_get_devname(struct ndctl_bus *bus); struct ndctl_bus *ndctl_bus_get_by_provider(struct ndctl_ctx *ctx, const char *provider); const char *ndctl_bus_get_cmd_name(struct ndctl_bus *bus, int cmd); int ndctl_bus_is_cmd_supported(struct ndctl_bus *bus, int cmd); unsigned int ndctl_bus_get_revision(struct ndctl_bus *bus); unsigned int ndctl_bus_get_id(struct ndctl_bus *bus); const char *ndctl_bus_get_provider(struct ndctl_bus *bus); enum ndctl_persistence_domain ndctl_bus_get_persistence_domain( struct ndctl_bus *bus); int ndctl_bus_wait_probe(struct ndctl_bus *bus); int ndctl_bus_wait_for_scrub_completion(struct ndctl_bus *bus); int ndctl_bus_poll_scrub_completion(struct ndctl_bus *bus, unsigned int poll_interval, unsigned int timeout); unsigned int ndctl_bus_get_scrub_count(struct ndctl_bus *bus); int ndctl_bus_get_scrub_state(struct ndctl_bus *bus); int ndctl_bus_start_scrub(struct ndctl_bus *bus); int ndctl_bus_has_error_injection(struct ndctl_bus *bus); enum ndctl_fwa_state ndctl_bus_get_fw_activate_state(struct ndctl_bus *bus); enum ndctl_fwa_method ndctl_bus_get_fw_activate_method(struct ndctl_bus *bus); int ndctl_bus_set_fw_activate_noidle(struct ndctl_bus *bus); int ndctl_bus_clear_fw_activate_noidle(struct ndctl_bus *bus); int ndctl_bus_set_fw_activate_nosuspend(struct ndctl_bus *bus); int ndctl_bus_clear_fw_activate_nosuspend(struct ndctl_bus *bus); int ndctl_bus_activate_firmware(struct ndctl_bus *bus, enum ndctl_fwa_method method); int ndctl_bus_nfit_translate_spa(struct ndctl_bus *bus, unsigned long long addr, unsigned int *handle, unsigned long long *dpa); struct ndctl_dimm; struct ndctl_dimm *ndctl_dimm_get_first(struct ndctl_bus *bus); struct ndctl_dimm *ndctl_dimm_get_next(struct ndctl_dimm *dimm); #define ndctl_dimm_foreach(bus, dimm) \ for (dimm = ndctl_dimm_get_first(bus); \ dimm != NULL; \ dimm = ndctl_dimm_get_next(dimm)) unsigned int ndctl_dimm_get_handle(struct ndctl_dimm *dimm); unsigned short ndctl_dimm_get_phys_id(struct ndctl_dimm *dimm); unsigned short ndctl_dimm_get_vendor(struct ndctl_dimm *dimm); unsigned short ndctl_dimm_get_device(struct ndctl_dimm *dimm); unsigned short ndctl_dimm_get_revision(struct ndctl_dimm *dimm); long long ndctl_dimm_get_dirty_shutdown(struct ndctl_dimm *dimm); unsigned short ndctl_dimm_get_subsystem_vendor(struct ndctl_dimm *dimm); unsigned short ndctl_dimm_get_subsystem_device(struct ndctl_dimm *dimm); unsigned short ndctl_dimm_get_manufacturing_date(struct ndctl_dimm *dimm); unsigned char ndctl_dimm_get_manufacturing_location(struct ndctl_dimm *dimm); unsigned short ndctl_dimm_get_subsystem_revision(struct ndctl_dimm *dimm); unsigned short ndctl_dimm_get_format(struct ndctl_dimm *dimm); int ndctl_dimm_get_formats(struct ndctl_dimm *dimm); int ndctl_dimm_get_formatN(struct ndctl_dimm *dimm, int i); unsigned int ndctl_dimm_get_major(struct ndctl_dimm *dimm); unsigned int ndctl_dimm_get_minor(struct ndctl_dimm *dimm); unsigned int ndctl_dimm_get_id(struct ndctl_dimm *dimm); const char *ndctl_dimm_get_unique_id(struct ndctl_dimm *dimm); unsigned int ndctl_dimm_get_serial(struct ndctl_dimm *dimm); const char *ndctl_dimm_get_cmd_name(struct ndctl_dimm *dimm, int cmd); int ndctl_dimm_is_cmd_supported(struct ndctl_dimm *dimm, int cmd); int ndctl_dimm_locked(struct ndctl_dimm *dimm); int ndctl_dimm_aliased(struct ndctl_dimm *dimm); int ndctl_dimm_has_errors(struct ndctl_dimm *dimm); int ndctl_dimm_has_notifications(struct ndctl_dimm *dimm); int ndctl_dimm_failed_save(struct ndctl_dimm *dimm); int ndctl_dimm_failed_arm(struct ndctl_dimm *dimm); int ndctl_dimm_failed_restore(struct ndctl_dimm *dimm); int ndctl_dimm_failed_map(struct ndctl_dimm *dimm); int ndctl_dimm_smart_pending(struct ndctl_dimm *dimm); int ndctl_dimm_failed_flush(struct ndctl_dimm *dimm); int ndctl_dimm_get_health_eventfd(struct ndctl_dimm *dimm); unsigned int ndctl_dimm_get_health(struct ndctl_dimm *dimm); unsigned int ndctl_dimm_get_flags(struct ndctl_dimm *dimm); unsigned int ndctl_dimm_get_event_flags(struct ndctl_dimm *dimm); int ndctl_dimm_is_flag_supported(struct ndctl_dimm *dimm, unsigned int flag); unsigned int ndctl_dimm_handle_get_node(struct ndctl_dimm *dimm); unsigned int ndctl_dimm_handle_get_socket(struct ndctl_dimm *dimm); unsigned int ndctl_dimm_handle_get_imc(struct ndctl_dimm *dimm); unsigned int ndctl_dimm_handle_get_channel(struct ndctl_dimm *dimm); unsigned int ndctl_dimm_handle_get_dimm(struct ndctl_dimm *dimm); const char *ndctl_dimm_get_devname(struct ndctl_dimm *dimm); struct ndctl_bus *ndctl_dimm_get_bus(struct ndctl_dimm *dimm); struct ndctl_ctx *ndctl_dimm_get_ctx(struct ndctl_dimm *dimm); struct ndctl_dimm *ndctl_dimm_get_by_handle(struct ndctl_bus *bus, unsigned int handle); struct ndctl_dimm *ndctl_bus_get_dimm_by_physical_address(struct ndctl_bus *bus, unsigned long long address); int ndctl_dimm_is_active(struct ndctl_dimm *dimm); int ndctl_dimm_is_enabled(struct ndctl_dimm *dimm); int ndctl_dimm_disable(struct ndctl_dimm *dimm); int ndctl_dimm_enable(struct ndctl_dimm *dimm); void ndctl_dimm_refresh_flags(struct ndctl_dimm *dimm); struct ndctl_cmd; struct ndctl_cmd *ndctl_bus_cmd_new_ars_cap(struct ndctl_bus *bus, unsigned long long address, unsigned long long len); struct ndctl_cmd *ndctl_bus_cmd_new_ars_start(struct ndctl_cmd *ars_cap, int type); struct ndctl_cmd *ndctl_bus_cmd_new_ars_status(struct ndctl_cmd *ars_cap); struct ndctl_range { unsigned long long address; unsigned long long length; }; unsigned int ndctl_cmd_ars_cap_get_size(struct ndctl_cmd *ars_cap); int ndctl_cmd_ars_cap_get_range(struct ndctl_cmd *ars_cap, struct ndctl_range *range); int ndctl_cmd_ars_in_progress(struct ndctl_cmd *ars_status); unsigned int ndctl_cmd_ars_num_records(struct ndctl_cmd *ars_stat); unsigned long long ndctl_cmd_ars_get_record_addr(struct ndctl_cmd *ars_stat, unsigned int rec_index); unsigned long long ndctl_cmd_ars_get_record_len(struct ndctl_cmd *ars_stat, unsigned int rec_index); struct ndctl_cmd *ndctl_bus_cmd_new_clear_error(unsigned long long address, unsigned long long len, struct ndctl_cmd *ars_cap); unsigned long long ndctl_cmd_clear_error_get_cleared( struct ndctl_cmd *clear_err); unsigned int ndctl_cmd_ars_cap_get_clear_unit(struct ndctl_cmd *ars_cap); int ndctl_cmd_ars_stat_get_flag_overflow(struct ndctl_cmd *ars_stat); /* * Note: ndctl_cmd_smart_get_temperature is an alias for * ndctl_cmd_smart_get_temperature */ /* * the ndctl.h definition of these are deprecated, libndctl.h is the * authoritative defintion. */ #define ND_SMART_HEALTH_VALID (1 << 0) #define ND_SMART_SPARES_VALID (1 << 1) #define ND_SMART_USED_VALID (1 << 2) #define ND_SMART_MTEMP_VALID (1 << 3) #define ND_SMART_TEMP_VALID ND_SMART_MTEMP_VALID #define ND_SMART_CTEMP_VALID (1 << 4) #define ND_SMART_SHUTDOWN_COUNT_VALID (1 << 5) #define ND_SMART_AIT_STATUS_VALID (1 << 6) #define ND_SMART_PTEMP_VALID (1 << 7) #define ND_SMART_ALARM_VALID (1 << 9) #define ND_SMART_SHUTDOWN_VALID (1 << 10) #define ND_SMART_VENDOR_VALID (1 << 11) #define ND_SMART_SPARE_TRIP (1 << 0) #define ND_SMART_MTEMP_TRIP (1 << 1) #define ND_SMART_TEMP_TRIP ND_SMART_MTEMP_TRIP #define ND_SMART_CTEMP_TRIP (1 << 2) #define ND_SMART_NON_CRITICAL_HEALTH (1 << 0) #define ND_SMART_CRITICAL_HEALTH (1 << 1) #define ND_SMART_FATAL_HEALTH (1 << 2) struct ndctl_cmd *ndctl_dimm_cmd_new_smart(struct ndctl_dimm *dimm); unsigned int ndctl_cmd_smart_get_flags(struct ndctl_cmd *cmd); unsigned int ndctl_cmd_smart_get_health(struct ndctl_cmd *cmd); unsigned int ndctl_cmd_smart_get_temperature(struct ndctl_cmd *cmd); unsigned int ndctl_cmd_smart_get_media_temperature(struct ndctl_cmd *cmd); unsigned int ndctl_cmd_smart_get_ctrl_temperature(struct ndctl_cmd *cmd); unsigned int ndctl_cmd_smart_get_spares(struct ndctl_cmd *cmd); unsigned int ndctl_cmd_smart_get_alarm_flags(struct ndctl_cmd *cmd); unsigned int ndctl_cmd_smart_get_life_used(struct ndctl_cmd *cmd); unsigned int ndctl_cmd_smart_get_shutdown_state(struct ndctl_cmd *cmd); unsigned int ndctl_cmd_smart_get_shutdown_count(struct ndctl_cmd *cmd); unsigned int ndctl_cmd_smart_get_vendor_size(struct ndctl_cmd *cmd); unsigned char *ndctl_cmd_smart_get_vendor_data(struct ndctl_cmd *cmd); struct ndctl_cmd *ndctl_dimm_cmd_new_smart_threshold(struct ndctl_dimm *dimm); unsigned int ndctl_cmd_smart_threshold_get_alarm_control(struct ndctl_cmd *cmd); unsigned int ndctl_cmd_smart_threshold_get_temperature(struct ndctl_cmd *cmd); unsigned int ndctl_cmd_smart_threshold_get_media_temperature(struct ndctl_cmd *cmd); unsigned int ndctl_cmd_smart_threshold_get_ctrl_temperature(struct ndctl_cmd *cmd); unsigned int ndctl_cmd_smart_threshold_get_spares(struct ndctl_cmd *cmd); struct ndctl_cmd *ndctl_dimm_cmd_new_smart_set_threshold(struct ndctl_cmd *cmd); unsigned int ndctl_cmd_smart_threshold_get_supported_alarms(struct ndctl_cmd *cmd); int ndctl_cmd_smart_threshold_set_alarm_control(struct ndctl_cmd *cmd, unsigned int val); int ndctl_cmd_smart_threshold_set_temperature(struct ndctl_cmd *cmd, unsigned int val); int ndctl_cmd_smart_threshold_set_media_temperature(struct ndctl_cmd *cmd, unsigned int val); int ndctl_cmd_smart_threshold_set_ctrl_temperature(struct ndctl_cmd *cmd, unsigned int val); int ndctl_cmd_smart_threshold_set_spares(struct ndctl_cmd *cmd, unsigned int val); struct ndctl_cmd *ndctl_dimm_cmd_new_smart_inject(struct ndctl_dimm *dimm); int ndctl_cmd_smart_inject_media_temperature(struct ndctl_cmd *cmd, bool enable, unsigned int mtemp); int ndctl_cmd_smart_inject_ctrl_temperature(struct ndctl_cmd *cmd, bool enable, unsigned int ctemp); int ndctl_cmd_smart_inject_spares(struct ndctl_cmd *cmd, bool enable, unsigned int spares); int ndctl_cmd_smart_inject_fatal(struct ndctl_cmd *cmd, bool enable); int ndctl_cmd_smart_inject_unsafe_shutdown(struct ndctl_cmd *cmd, bool enable); /* Returns a bitmap of ND_SMART_INJECT_* supported */ int ndctl_dimm_smart_inject_supported(struct ndctl_dimm *dimm); struct ndctl_cmd *ndctl_dimm_cmd_new_vendor_specific(struct ndctl_dimm *dimm, unsigned int opcode, size_t input_size, size_t output_size); ssize_t ndctl_cmd_vendor_set_input(struct ndctl_cmd *cmd, void *buf, unsigned int len); ssize_t ndctl_cmd_vendor_get_output_size(struct ndctl_cmd *cmd); ssize_t ndctl_cmd_vendor_get_output(struct ndctl_cmd *cmd, void *buf, unsigned int len); struct ndctl_cmd *ndctl_dimm_cmd_new_cfg_size(struct ndctl_dimm *dimm); struct ndctl_cmd *ndctl_dimm_cmd_new_cfg_read(struct ndctl_cmd *cfg_size); struct ndctl_cmd *ndctl_dimm_cmd_new_cfg_write(struct ndctl_cmd *cfg_read); int ndctl_dimm_zero_labels(struct ndctl_dimm *dimm); int ndctl_dimm_zero_label_extent(struct ndctl_dimm *dimm, unsigned int len, unsigned int offset); struct ndctl_cmd *ndctl_dimm_read_labels(struct ndctl_dimm *dimm); struct ndctl_cmd *ndctl_dimm_read_label_index(struct ndctl_dimm *dimm); struct ndctl_cmd *ndctl_dimm_read_label_extent(struct ndctl_dimm *dimm, unsigned int len, unsigned int offset); int ndctl_dimm_validate_labels(struct ndctl_dimm *dimm); enum ndctl_namespace_version { NDCTL_NS_VERSION_1_1, NDCTL_NS_VERSION_1_2, }; int ndctl_dimm_init_labels(struct ndctl_dimm *dimm, enum ndctl_namespace_version v); unsigned long ndctl_dimm_get_available_labels(struct ndctl_dimm *dimm); unsigned int ndctl_dimm_sizeof_namespace_label(struct ndctl_dimm *dimm); unsigned int ndctl_dimm_sizeof_namespace_index(struct ndctl_dimm *dimm); unsigned int ndctl_cmd_cfg_size_get_size(struct ndctl_cmd *cfg_size); ssize_t ndctl_cmd_cfg_read_get_data(struct ndctl_cmd *cfg_read, void *buf, unsigned int len, unsigned int offset); ssize_t ndctl_cmd_cfg_read_get_size(struct ndctl_cmd *cfg_read); int ndctl_cmd_cfg_read_set_extent(struct ndctl_cmd *cfg_read, unsigned int len, unsigned int offset); int ndctl_cmd_cfg_write_set_extent(struct ndctl_cmd *cfg_write, unsigned int len, unsigned int offset); ssize_t ndctl_cmd_cfg_write_set_data(struct ndctl_cmd *cfg_write, void *buf, unsigned int len, unsigned int offset); ssize_t ndctl_cmd_cfg_write_zero_data(struct ndctl_cmd *cfg_write); void ndctl_cmd_unref(struct ndctl_cmd *cmd); void ndctl_cmd_ref(struct ndctl_cmd *cmd); int ndctl_cmd_get_type(struct ndctl_cmd *cmd); int ndctl_cmd_get_status(struct ndctl_cmd *cmd); unsigned int ndctl_cmd_get_firmware_status(struct ndctl_cmd *cmd); int ndctl_cmd_submit(struct ndctl_cmd *cmd); struct badblock { unsigned long long offset; unsigned int len; }; struct ndctl_region; struct ndctl_region *ndctl_region_get_first(struct ndctl_bus *bus); struct ndctl_region *ndctl_region_get_next(struct ndctl_region *region); #define ndctl_region_foreach(bus, region) \ for (region = ndctl_region_get_first(bus); \ region != NULL; \ region = ndctl_region_get_next(region)) struct badblock *ndctl_region_get_first_badblock(struct ndctl_region *region); struct badblock *ndctl_region_get_next_badblock(struct ndctl_region *region); #define ndctl_region_badblock_foreach(region, badblock) \ for (badblock = ndctl_region_get_first_badblock(region); \ badblock != NULL; \ badblock = ndctl_region_get_next_badblock(region)) unsigned int ndctl_region_get_id(struct ndctl_region *region); const char *ndctl_region_get_devname(struct ndctl_region *region); unsigned int ndctl_region_get_interleave_ways(struct ndctl_region *region); unsigned int ndctl_region_get_mappings(struct ndctl_region *region); unsigned long long ndctl_region_get_size(struct ndctl_region *region); unsigned long long ndctl_region_get_available_size(struct ndctl_region *region); unsigned long long ndctl_region_get_max_available_extent( struct ndctl_region *region); unsigned int ndctl_region_get_range_index(struct ndctl_region *region); unsigned int ndctl_region_get_type(struct ndctl_region *region); struct ndctl_namespace *ndctl_region_get_namespace_seed( struct ndctl_region *region); int ndctl_region_get_ro(struct ndctl_region *region); int ndctl_region_set_ro(struct ndctl_region *region, int ro); unsigned long ndctl_region_get_align(struct ndctl_region *region); int ndctl_region_set_align(struct ndctl_region *region, unsigned long align); unsigned long long ndctl_region_get_resource(struct ndctl_region *region); struct ndctl_btt *ndctl_region_get_btt_seed(struct ndctl_region *region); struct ndctl_pfn *ndctl_region_get_pfn_seed(struct ndctl_region *region); unsigned int ndctl_region_get_nstype(struct ndctl_region *region); const char *ndctl_region_get_type_name(struct ndctl_region *region); struct ndctl_bus *ndctl_region_get_bus(struct ndctl_region *region); struct ndctl_ctx *ndctl_region_get_ctx(struct ndctl_region *region); struct ndctl_dimm *ndctl_region_get_first_dimm(struct ndctl_region *region); struct ndctl_dimm *ndctl_region_get_next_dimm(struct ndctl_region *region, struct ndctl_dimm *dimm); int ndctl_region_get_numa_node(struct ndctl_region *region); int ndctl_region_has_numa(struct ndctl_region *region); int ndctl_region_get_target_node(struct ndctl_region *region); struct ndctl_region *ndctl_bus_get_region_by_physical_address(struct ndctl_bus *bus, unsigned long long address); #define ndctl_dimm_foreach_in_region(region, dimm) \ for (dimm = ndctl_region_get_first_dimm(region); \ dimm != NULL; \ dimm = ndctl_region_get_next_dimm(region, dimm)) enum ndctl_persistence_domain ndctl_region_get_persistence_domain( struct ndctl_region *region); int ndctl_region_is_enabled(struct ndctl_region *region); int ndctl_region_enable(struct ndctl_region *region); int ndctl_region_disable_invalidate(struct ndctl_region *region); int ndctl_region_disable_preserve(struct ndctl_region *region); void ndctl_region_cleanup(struct ndctl_region *region); int ndctl_region_deep_flush(struct ndctl_region *region); struct ndctl_interleave_set; struct ndctl_interleave_set *ndctl_region_get_interleave_set( struct ndctl_region *region); struct ndctl_interleave_set *ndctl_interleave_set_get_first( struct ndctl_bus *bus); struct ndctl_interleave_set *ndctl_interleave_set_get_next( struct ndctl_interleave_set *iset); #define ndctl_interleave_set_foreach(bus, iset) \ for (iset = ndctl_interleave_set_get_first(bus); \ iset != NULL; \ iset = ndctl_interleave_set_get_next(iset)) #define ndctl_dimm_foreach_in_interleave_set(iset, dimm) \ for (dimm = ndctl_interleave_set_get_first_dimm(iset); \ dimm != NULL; \ dimm = ndctl_interleave_set_get_next_dimm(iset, dimm)) int ndctl_interleave_set_is_active(struct ndctl_interleave_set *iset); unsigned long long ndctl_interleave_set_get_cookie( struct ndctl_interleave_set *iset); struct ndctl_region *ndctl_interleave_set_get_region( struct ndctl_interleave_set *iset); struct ndctl_dimm *ndctl_interleave_set_get_first_dimm( struct ndctl_interleave_set *iset); struct ndctl_dimm *ndctl_interleave_set_get_next_dimm( struct ndctl_interleave_set *iset, struct ndctl_dimm *dimm); struct ndctl_mapping; struct ndctl_mapping *ndctl_mapping_get_first(struct ndctl_region *region); struct ndctl_mapping *ndctl_mapping_get_next(struct ndctl_mapping *mapping); #define ndctl_mapping_foreach(region, mapping) \ for (mapping = ndctl_mapping_get_first(region); \ mapping != NULL; \ mapping = ndctl_mapping_get_next(mapping)) struct ndctl_dimm *ndctl_mapping_get_dimm(struct ndctl_mapping *mapping); struct ndctl_ctx *ndctl_mapping_get_ctx(struct ndctl_mapping *mapping); struct ndctl_bus *ndctl_mapping_get_bus(struct ndctl_mapping *mapping); struct ndctl_region *ndctl_mapping_get_region(struct ndctl_mapping *mapping); unsigned long long ndctl_mapping_get_offset(struct ndctl_mapping *mapping); unsigned long long ndctl_mapping_get_length(struct ndctl_mapping *mapping); int ndctl_mapping_get_position(struct ndctl_mapping *mapping); struct ndctl_namespace; struct ndctl_namespace *ndctl_namespace_get_first(struct ndctl_region *region); struct ndctl_namespace *ndctl_namespace_get_next(struct ndctl_namespace *ndns); #define ndctl_namespace_foreach(region, ndns) \ for (ndns = ndctl_namespace_get_first(region); \ ndns != NULL; \ ndns = ndctl_namespace_get_next(ndns)) #define ndctl_namespace_foreach_safe(region, ndns, _ndns) \ for (ndns = ndctl_namespace_get_first(region), \ _ndns = ndns ? ndctl_namespace_get_next(ndns) : NULL; \ ndns != NULL; \ ndns = _ndns, \ _ndns = _ndns ? ndctl_namespace_get_next(_ndns) : NULL) struct badblock *ndctl_namespace_get_first_badblock(struct ndctl_namespace *ndns); struct badblock *ndctl_namespace_get_next_badblock(struct ndctl_namespace *ndns); #define ndctl_namespace_badblock_foreach(ndns, badblock) \ for (badblock = ndctl_namespace_get_first_badblock(ndns); \ badblock != NULL; \ badblock = ndctl_namespace_get_next_badblock(ndns)) struct ndctl_ctx *ndctl_namespace_get_ctx(struct ndctl_namespace *ndns); struct ndctl_bus *ndctl_namespace_get_bus(struct ndctl_namespace *ndns); struct ndctl_region *ndctl_namespace_get_region(struct ndctl_namespace *ndns); struct ndctl_btt *ndctl_namespace_get_btt(struct ndctl_namespace *ndns); struct ndctl_pfn *ndctl_namespace_get_pfn(struct ndctl_namespace *ndns); struct ndctl_dax *ndctl_namespace_get_dax(struct ndctl_namespace *ndns); unsigned int ndctl_namespace_get_id(struct ndctl_namespace *ndns); const char *ndctl_namespace_get_devname(struct ndctl_namespace *ndns); unsigned int ndctl_namespace_get_type(struct ndctl_namespace *ndns); const char *ndctl_namespace_get_type_name(struct ndctl_namespace *ndns); const char *ndctl_namespace_get_block_device(struct ndctl_namespace *ndns); enum ndctl_namespace_mode { NDCTL_NS_MODE_MEMORY, NDCTL_NS_MODE_FSDAX = NDCTL_NS_MODE_MEMORY, NDCTL_NS_MODE_SAFE, NDCTL_NS_MODE_SECTOR = NDCTL_NS_MODE_SAFE, NDCTL_NS_MODE_RAW, NDCTL_NS_MODE_DAX, NDCTL_NS_MODE_DEVDAX = NDCTL_NS_MODE_DAX, NDCTL_NS_MODE_UNKNOWN, /* must be last entry */ }; enum ndctl_namespace_mode ndctl_namespace_get_mode( struct ndctl_namespace *ndns); enum ndctl_namespace_mode ndctl_namespace_get_enforce_mode( struct ndctl_namespace *ndns); int ndctl_namespace_set_enforce_mode(struct ndctl_namespace *ndns, enum ndctl_namespace_mode mode); int ndctl_namespace_is_enabled(struct ndctl_namespace *ndns); int ndctl_namespace_enable(struct ndctl_namespace *ndns); int ndctl_namespace_disable(struct ndctl_namespace *ndns); int ndctl_namespace_disable_invalidate(struct ndctl_namespace *ndns); int ndctl_namespace_disable_safe(struct ndctl_namespace *ndns); bool ndctl_namespace_is_active(struct ndctl_namespace *ndns); int ndctl_namespace_is_valid(struct ndctl_namespace *ndns); int ndctl_namespace_is_configured(struct ndctl_namespace *ndns); int ndctl_namespace_is_configuration_idle(struct ndctl_namespace *ndns); int ndctl_namespace_delete(struct ndctl_namespace *ndns); int ndctl_namespace_set_uuid(struct ndctl_namespace *ndns, uuid_t uu); void ndctl_namespace_get_uuid(struct ndctl_namespace *ndns, uuid_t uu); const char *ndctl_namespace_get_alt_name(struct ndctl_namespace *ndns); int ndctl_namespace_set_alt_name(struct ndctl_namespace *ndns, const char *alt_name); unsigned long long ndctl_namespace_get_size(struct ndctl_namespace *ndns); unsigned long long ndctl_namespace_get_resource(struct ndctl_namespace *ndns); int ndctl_namespace_set_size(struct ndctl_namespace *ndns, unsigned long long size); unsigned int ndctl_namespace_get_supported_sector_size( struct ndctl_namespace *ndns, int i); unsigned int ndctl_namespace_get_sector_size(struct ndctl_namespace *ndns); int ndctl_namespace_get_num_sector_sizes(struct ndctl_namespace *ndns); int ndctl_namespace_set_sector_size(struct ndctl_namespace *ndns, unsigned int sector_size); int ndctl_namespace_get_raw_mode(struct ndctl_namespace *ndns); int ndctl_namespace_set_raw_mode(struct ndctl_namespace *ndns, int raw_mode); int ndctl_namespace_get_numa_node(struct ndctl_namespace *ndns); int ndctl_namespace_get_target_node(struct ndctl_namespace *ndns); int ndctl_namespace_inject_error(struct ndctl_namespace *ndns, unsigned long long block, unsigned long long count, bool notify); int ndctl_namespace_inject_error2(struct ndctl_namespace *ndns, unsigned long long block, unsigned long long count, unsigned int flags); int ndctl_namespace_uninject_error(struct ndctl_namespace *ndns, unsigned long long block, unsigned long long count); int ndctl_namespace_uninject_error2(struct ndctl_namespace *ndns, unsigned long long block, unsigned long long count, unsigned int flags); int ndctl_namespace_injection_status(struct ndctl_namespace *ndns); enum ndctl_namespace_inject_flags { NDCTL_NS_INJECT_NOTIFY = 0, NDCTL_NS_INJECT_SATURATE, }; struct ndctl_bb; unsigned long long ndctl_bb_get_block(struct ndctl_bb *bb); unsigned long long ndctl_bb_get_count(struct ndctl_bb *bb); struct ndctl_bb *ndctl_namespace_injection_get_first_bb( struct ndctl_namespace *ndns); struct ndctl_bb *ndctl_namespace_injection_get_next_bb( struct ndctl_namespace *ndns, struct ndctl_bb *bb); #define ndctl_namespace_bb_foreach(ndns, bb) \ for (bb = ndctl_namespace_injection_get_first_bb(ndns); \ bb != NULL; \ bb = ndctl_namespace_injection_get_next_bb(ndns, bb)) int ndctl_namespace_write_cache_is_enabled(struct ndctl_namespace *ndns); int ndctl_namespace_enable_write_cache(struct ndctl_namespace *ndns); int ndctl_namespace_disable_write_cache(struct ndctl_namespace *ndns); struct ndctl_btt; struct ndctl_btt *ndctl_btt_get_first(struct ndctl_region *region); struct ndctl_btt *ndctl_btt_get_next(struct ndctl_btt *btt); #define ndctl_btt_foreach(region, btt) \ for (btt = ndctl_btt_get_first(region); \ btt != NULL; \ btt = ndctl_btt_get_next(btt)) #define ndctl_btt_foreach_safe(region, btt, _btt) \ for (btt = ndctl_btt_get_first(region), \ _btt = btt ? ndctl_btt_get_next(btt) : NULL; \ btt != NULL; \ btt = _btt, \ _btt = _btt ? ndctl_btt_get_next(_btt) : NULL) struct ndctl_ctx *ndctl_btt_get_ctx(struct ndctl_btt *btt); struct ndctl_bus *ndctl_btt_get_bus(struct ndctl_btt *btt); struct ndctl_region *ndctl_btt_get_region(struct ndctl_btt *btt); unsigned int ndctl_btt_get_id(struct ndctl_btt *btt); unsigned int ndctl_btt_get_supported_sector_size(struct ndctl_btt *btt, int i); unsigned int ndctl_btt_get_sector_size(struct ndctl_btt *btt); int ndctl_btt_get_num_sector_sizes(struct ndctl_btt *btt); struct ndctl_namespace *ndctl_btt_get_namespace(struct ndctl_btt *btt); void ndctl_btt_get_uuid(struct ndctl_btt *btt, uuid_t uu); unsigned long long ndctl_btt_get_size(struct ndctl_btt *btt); int ndctl_btt_is_enabled(struct ndctl_btt *btt); int ndctl_btt_is_valid(struct ndctl_btt *btt); const char *ndctl_btt_get_devname(struct ndctl_btt *btt); const char *ndctl_btt_get_block_device(struct ndctl_btt *btt); int ndctl_btt_set_uuid(struct ndctl_btt *btt, uuid_t uu); int ndctl_btt_set_sector_size(struct ndctl_btt *btt, unsigned int sector_size); int ndctl_btt_set_namespace(struct ndctl_btt *btt, struct ndctl_namespace *ndns); int ndctl_btt_enable(struct ndctl_btt *btt); int ndctl_btt_delete(struct ndctl_btt *btt); int ndctl_btt_is_configured(struct ndctl_btt *btt); struct ndctl_pfn; struct ndctl_pfn *ndctl_pfn_get_first(struct ndctl_region *region); struct ndctl_pfn *ndctl_pfn_get_next(struct ndctl_pfn *pfn); #define ndctl_pfn_foreach(region, pfn) \ for (pfn = ndctl_pfn_get_first(region); \ pfn != NULL; \ pfn = ndctl_pfn_get_next(pfn)) #define ndctl_pfn_foreach_safe(region, pfn, _pfn) \ for (pfn = ndctl_pfn_get_first(region), \ _pfn = ndctl_pfn_get_next(pfn); \ pfn != NULL; \ pfn = _pfn, \ _pfn = _pfn ? ndctl_pfn_get_next(_pfn) : NULL) struct ndctl_ctx *ndctl_pfn_get_ctx(struct ndctl_pfn *pfn); struct ndctl_bus *ndctl_pfn_get_bus(struct ndctl_pfn *pfn); struct ndctl_region *ndctl_pfn_get_region(struct ndctl_pfn *pfn); unsigned int ndctl_pfn_get_id(struct ndctl_pfn *pfn); int ndctl_pfn_is_enabled(struct ndctl_pfn *pfn); int ndctl_pfn_is_valid(struct ndctl_pfn *pfn); const char *ndctl_pfn_get_devname(struct ndctl_pfn *pfn); const char *ndctl_pfn_get_block_device(struct ndctl_pfn *pfn); enum ndctl_pfn_loc { NDCTL_PFN_LOC_NONE, NDCTL_PFN_LOC_RAM, NDCTL_PFN_LOC_PMEM, }; int ndctl_pfn_set_location(struct ndctl_pfn *pfn, enum ndctl_pfn_loc loc); enum ndctl_pfn_loc ndctl_pfn_get_location(struct ndctl_pfn *pfn); int ndctl_pfn_set_uuid(struct ndctl_pfn *pfn, uuid_t uu); void ndctl_pfn_get_uuid(struct ndctl_pfn *pfn, uuid_t uu); int ndctl_pfn_has_align(struct ndctl_pfn *pfn); int ndctl_pfn_set_align(struct ndctl_pfn *pfn, unsigned long align); int ndctl_pfn_get_num_alignments(struct ndctl_pfn *pfn); unsigned long ndctl_pfn_get_align(struct ndctl_pfn *pfn); unsigned long ndctl_pfn_get_supported_alignment(struct ndctl_pfn *pfn, int i); unsigned long long ndctl_pfn_get_resource(struct ndctl_pfn *pfn); unsigned long long ndctl_pfn_get_size(struct ndctl_pfn *pfn); int ndctl_pfn_set_namespace(struct ndctl_pfn *pfn, struct ndctl_namespace *ndns); struct ndctl_namespace *ndctl_pfn_get_namespace(struct ndctl_pfn *pfn); int ndctl_pfn_enable(struct ndctl_pfn *pfn); int ndctl_pfn_delete(struct ndctl_pfn *pfn); int ndctl_pfn_is_configured(struct ndctl_pfn *pfn); #define ndctl_dax_foreach(region, dax) \ for (dax = ndctl_dax_get_first(region); \ dax != NULL; \ dax = ndctl_dax_get_next(dax)) #define ndctl_dax_foreach_safe(region, dax, _dax) \ for (dax = ndctl_dax_get_first(region), \ _dax = ndctl_dax_get_next(dax); \ dax != NULL; \ dax = _dax, \ _dax = _dax ? ndctl_dax_get_next(_dax) : NULL) struct ndctl_dax *ndctl_region_get_dax_seed(struct ndctl_region *region); struct ndctl_dax *ndctl_namespace_get_dax(struct ndctl_namespace *ndns); struct ndctl_dax *ndctl_dax_get_first(struct ndctl_region *region); struct ndctl_dax *ndctl_dax_get_next(struct ndctl_dax *dax); unsigned int ndctl_dax_get_id(struct ndctl_dax *dax); struct ndctl_namespace *ndctl_dax_get_namespace(struct ndctl_dax *dax); void ndctl_dax_get_uuid(struct ndctl_dax *dax, uuid_t uu); unsigned long long ndctl_dax_get_size(struct ndctl_dax *dax); unsigned long long ndctl_dax_get_resource(struct ndctl_dax *dax); int ndctl_dax_set_uuid(struct ndctl_dax *dax, uuid_t uu); enum ndctl_pfn_loc ndctl_dax_get_location(struct ndctl_dax *dax); int ndctl_dax_set_location(struct ndctl_dax *dax, enum ndctl_pfn_loc loc); int ndctl_dax_get_num_alignments(struct ndctl_dax *dax); unsigned long ndctl_dax_get_align(struct ndctl_dax *dax); unsigned long ndctl_dax_get_supported_alignment(struct ndctl_dax *dax, int i); int ndctl_dax_has_align(struct ndctl_dax *dax); int ndctl_dax_set_align(struct ndctl_dax *dax, unsigned long align); int ndctl_dax_set_namespace(struct ndctl_dax *dax, struct ndctl_namespace *ndns); struct ndctl_bus *ndctl_dax_get_bus(struct ndctl_dax *dax); struct ndctl_ctx *ndctl_dax_get_ctx(struct ndctl_dax *dax); const char *ndctl_dax_get_devname(struct ndctl_dax *dax); int ndctl_dax_is_valid(struct ndctl_dax *dax); int ndctl_dax_is_enabled(struct ndctl_dax *dax); struct ndctl_region *ndctl_dax_get_region(struct ndctl_dax *dax); int ndctl_dax_enable(struct ndctl_dax *dax); int ndctl_dax_delete(struct ndctl_dax *dax); int ndctl_dax_is_configured(struct ndctl_dax *dax); struct daxctl_region *ndctl_dax_get_daxctl_region(struct ndctl_dax *dax); enum ND_FW_STATUS { FW_SUCCESS = 0, /* success */ FW_ENOTSUPP, /* not supported */ FW_ENOTEXIST, /* device not exist */ FW_EINVAL, /* invalid input */ FW_EHWERR, /* hardware error */ FW_ERETRY, /* try again */ FW_EUNKNOWN, /* unknown reason */ FW_ENORES, /* out of resource */ FW_ENOTREADY, /* hardware not ready */ FW_EBUSY, /* firmware inprogress */ FW_EINVAL_CTX, /* invalid context passed in */ FW_ALREADY_DONE, /* firmware already updated */ FW_EBADFW, /* firmware failed verification */ FW_ABORTED, /* update sequence aborted success */ FW_ESEQUENCE, /* update sequence incorrect */ }; struct ndctl_cmd *ndctl_dimm_cmd_new_fw_get_info(struct ndctl_dimm *dimm); struct ndctl_cmd *ndctl_dimm_cmd_new_fw_start_update(struct ndctl_dimm *dimm); struct ndctl_cmd *ndctl_dimm_cmd_new_fw_send(struct ndctl_cmd *start, unsigned int offset, unsigned int len, void *data); struct ndctl_cmd *ndctl_dimm_cmd_new_fw_finish(struct ndctl_cmd *start); struct ndctl_cmd *ndctl_dimm_cmd_new_fw_abort(struct ndctl_cmd *start); struct ndctl_cmd *ndctl_dimm_cmd_new_fw_finish_query(struct ndctl_cmd *start); unsigned int ndctl_cmd_fw_info_get_storage_size(struct ndctl_cmd *cmd); unsigned int ndctl_cmd_fw_info_get_max_send_len(struct ndctl_cmd *cmd); unsigned int ndctl_cmd_fw_info_get_query_interval(struct ndctl_cmd *cmd); unsigned int ndctl_cmd_fw_info_get_max_query_time(struct ndctl_cmd *cmd); unsigned long long ndctl_cmd_fw_info_get_run_version(struct ndctl_cmd *cmd); unsigned long long ndctl_cmd_fw_info_get_updated_version(struct ndctl_cmd *cmd); unsigned int ndctl_cmd_fw_start_get_context(struct ndctl_cmd *cmd); unsigned long long ndctl_cmd_fw_fquery_get_fw_rev(struct ndctl_cmd *cmd); enum ND_FW_STATUS ndctl_cmd_fw_xlat_firmware_status(struct ndctl_cmd *cmd); struct ndctl_cmd *ndctl_dimm_cmd_new_ack_shutdown_count(struct ndctl_dimm *dimm); int ndctl_dimm_fw_update_supported(struct ndctl_dimm *dimm); enum ndctl_fwa_result { NDCTL_FWA_RESULT_INVALID, NDCTL_FWA_RESULT_NONE, NDCTL_FWA_RESULT_SUCCESS, NDCTL_FWA_RESULT_NOTSTAGED, NDCTL_FWA_RESULT_NEEDRESET, NDCTL_FWA_RESULT_FAIL, }; enum ndctl_fwa_state ndctl_dimm_get_fw_activate_state(struct ndctl_dimm *dimm); enum ndctl_fwa_result ndctl_dimm_get_fw_activate_result(struct ndctl_dimm *dimm); enum ndctl_fwa_state ndctl_dimm_fw_activate_disarm(struct ndctl_dimm *dimm); enum ndctl_fwa_state ndctl_dimm_fw_activate_arm(struct ndctl_dimm *dimm); int ndctl_cmd_xlat_firmware_status(struct ndctl_cmd *cmd); int ndctl_cmd_submit_xlat(struct ndctl_cmd *cmd); #define ND_PASSPHRASE_SIZE 32 #define ND_KEY_DESC_LEN 22 #define ND_KEY_DESC_PREFIX 7 enum ndctl_security_state { NDCTL_SECURITY_INVALID = -1, NDCTL_SECURITY_DISABLED = 0, NDCTL_SECURITY_UNLOCKED, NDCTL_SECURITY_LOCKED, NDCTL_SECURITY_FROZEN, NDCTL_SECURITY_OVERWRITE, }; enum ndctl_security_state ndctl_dimm_get_security(struct ndctl_dimm *dimm); bool ndctl_dimm_security_is_frozen(struct ndctl_dimm *dimm); int ndctl_dimm_update_passphrase(struct ndctl_dimm *dimm, long ckey, long nkey); int ndctl_dimm_disable_passphrase(struct ndctl_dimm *dimm, long key); int ndctl_dimm_disable_master_passphrase(struct ndctl_dimm *dimm, long key); int ndctl_dimm_freeze_security(struct ndctl_dimm *dimm); int ndctl_dimm_secure_erase(struct ndctl_dimm *dimm, long key); int ndctl_dimm_overwrite(struct ndctl_dimm *dimm, long key); int ndctl_dimm_wait_overwrite(struct ndctl_dimm *dimm); int ndctl_dimm_update_master_passphrase(struct ndctl_dimm *dimm, long ckey, long nkey); int ndctl_dimm_master_secure_erase(struct ndctl_dimm *dimm, long key); #define ND_KEY_DESC_SIZE 128 #define ND_KEY_CMD_SIZE 128 #define NUMA_NO_NODE (-1) #define NUMA_NO_ATTR (-2) #ifdef __cplusplus } /* extern "C" */ #endif #endif ndctl-81/ndctl/list.c000066400000000000000000000322521476737544500146650ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2015-2020 Intel Corporation. All rights reserved. #include #include #include #include #include #include #include #include #include #include #include "ndctl.h" #include "filter.h" #include "json.h" static struct { bool buses; bool dimms; bool regions; bool namespaces; bool idle; bool health; bool dax; bool media_errors; bool human; bool firmware; bool capabilities; bool configured; int verbose; } list; static unsigned long listopts_to_flags(void) { unsigned long flags = 0; if (list.idle) flags |= UTIL_JSON_IDLE; if (list.configured) flags |= UTIL_JSON_CONFIGURED; if (list.media_errors) flags |= UTIL_JSON_MEDIA_ERRORS; if (list.dax) flags |= UTIL_JSON_DAX | UTIL_JSON_DAX_DEVS; if (list.human) flags |= UTIL_JSON_HUMAN; if (list.verbose) flags |= UTIL_JSON_VERBOSE; if (list.capabilities) flags |= UTIL_JSON_CAPABILITIES; if (list.firmware) flags |= UTIL_JSON_FIRMWARE; return flags; } static struct ndctl_filter_params param; static int did_fail; #define fail(fmt, ...) \ do { \ did_fail = 1; \ fprintf(stderr, "ndctl-%s:%s:%d: " fmt, \ VERSION, __func__, __LINE__, ##__VA_ARGS__); \ } while (0) static struct json_object *region_to_json(struct ndctl_region *region, unsigned long flags) { struct json_object *jregion = json_object_new_object(); struct json_object *jobj, *jbbs, *jmappings = NULL; struct ndctl_interleave_set *iset; struct ndctl_mapping *mapping; unsigned int bb_count = 0; unsigned long long extent, align; enum ndctl_persistence_domain pd; int numa, target; if (!jregion) return NULL; jobj = json_object_new_string(ndctl_region_get_devname(region)); if (!jobj) goto err; json_object_object_add(jregion, "dev", jobj); jobj = util_json_object_size(ndctl_region_get_size(region), flags); if (!jobj) goto err; json_object_object_add(jregion, "size", jobj); align = ndctl_region_get_align(region); if (align < ULLONG_MAX) { jobj = util_json_object_size(align, flags); if (!jobj) goto err; json_object_object_add(jregion, "align", jobj); } jobj = util_json_object_size(ndctl_region_get_available_size(region), flags); if (!jobj) goto err; json_object_object_add(jregion, "available_size", jobj); extent = ndctl_region_get_max_available_extent(region); if (extent != ULLONG_MAX) { jobj = util_json_object_size(extent, flags); if (!jobj) goto err; json_object_object_add(jregion, "max_available_extent", jobj); } switch (ndctl_region_get_type(region)) { case ND_DEVICE_REGION_PMEM: jobj = json_object_new_string("pmem"); break; case ND_DEVICE_REGION_BLK: jobj = json_object_new_string("blk"); break; default: jobj = NULL; } if (!jobj) goto err; json_object_object_add(jregion, "type", jobj); numa = ndctl_region_get_numa_node(region); if (numa >= 0 && flags & UTIL_JSON_VERBOSE) { jobj = json_object_new_int(numa); if (jobj) json_object_object_add(jregion, "numa_node", jobj); } target = ndctl_region_get_target_node(region); if (target >= 0 && flags & UTIL_JSON_VERBOSE) { jobj = json_object_new_int(target); if (jobj) json_object_object_add(jregion, "target_node", jobj); } iset = ndctl_region_get_interleave_set(region); if (iset) { jobj = util_json_object_hex( ndctl_interleave_set_get_cookie(iset), flags); if (!jobj) fail("\n"); else json_object_object_add(jregion, "iset_id", jobj); } ndctl_mapping_foreach(region, mapping) { struct ndctl_dimm *dimm = ndctl_mapping_get_dimm(mapping); struct json_object *jmapping; if (!list.dimms) break; if (!util_dimm_filter(dimm, param.dimm)) continue; if (!list.configured && !list.idle && !ndctl_dimm_is_enabled(dimm)) continue; if (!jmappings) { jmappings = json_object_new_array(); if (!jmappings) { fail("\n"); continue; } json_object_object_add(jregion, "mappings", jmappings); } jmapping = util_mapping_to_json(mapping, listopts_to_flags()); if (!jmapping) { fail("\n"); continue; } json_object_array_add(jmappings, jmapping); } if (!ndctl_region_is_enabled(region)) { jobj = json_object_new_string("disabled"); if (!jobj) goto err; json_object_object_add(jregion, "state", jobj); } jbbs = util_region_badblocks_to_json(region, &bb_count, flags); if (bb_count) { jobj = json_object_new_int(bb_count); if (!jobj) { json_object_put(jbbs); goto err; } json_object_object_add(jregion, "badblock_count", jobj); } if ((flags & UTIL_JSON_MEDIA_ERRORS) && jbbs) json_object_object_add(jregion, "badblocks", jbbs); if (flags & UTIL_JSON_CAPABILITIES) { jobj = util_region_capabilities_to_json(region); if (jobj) json_object_object_add(jregion, "capabilities", jobj); } pd = ndctl_region_get_persistence_domain(region); switch (pd) { case PERSISTENCE_CPU_CACHE: jobj = json_object_new_string("cpu_cache"); break; case PERSISTENCE_MEM_CTRL: jobj = json_object_new_string("memory_controller"); break; case PERSISTENCE_NONE: jobj = json_object_new_string("none"); break; default: jobj = json_object_new_string("unknown"); break; } if (jobj) json_object_object_add(jregion, "persistence_domain", jobj); return jregion; err: fail("\n"); json_object_put(jregion); return NULL; } static void filter_namespace(struct ndctl_namespace *ndns, struct ndctl_filter_ctx *ctx) { struct json_object *jndns; struct list_filter_arg *lfa = ctx->list; struct json_object *container = lfa->jregion ? lfa->jregion : lfa->jbus; unsigned long long size = ndctl_namespace_get_size(ndns); if (ndctl_namespace_is_active(ndns)) /* pass */; else if (list.idle) /* pass */; else if (list.configured && (size > 0 && size < ULLONG_MAX)) /* pass */; else return; if (!lfa->jnamespaces) { lfa->jnamespaces = json_object_new_array(); if (!lfa->jnamespaces) { fail("\n"); return; } if (container) json_object_object_add(container, "namespaces", lfa->jnamespaces); } jndns = util_namespace_to_json(ndns, lfa->flags); if (!jndns) { fail("\n"); return; } json_object_array_add(lfa->jnamespaces, jndns); } static bool filter_region(struct ndctl_region *region, struct ndctl_filter_ctx *ctx) { struct list_filter_arg *lfa = ctx->list; struct json_object *jbus = lfa->jbus; struct json_object *jregion; if (!list.regions) return true; if (!list.configured && !list.idle && !ndctl_region_is_enabled(region)) return true; if (!lfa->jregions) { lfa->jregions = json_object_new_array(); if (!lfa->jregions) { fail("\n"); return false; } if (jbus) json_object_object_add(jbus, "regions", lfa->jregions); } jregion = region_to_json(region, lfa->flags); if (!jregion) { fail("\n"); return false; } lfa->jregion = jregion; /* * We've started a new region, any previous jnamespaces will * have been parented to the last region. Clear out jnamespaces * so we start a new array per region. */ lfa->jnamespaces = NULL; /* * Without a bus we are collecting regions anonymously across * the platform. */ json_object_array_add(lfa->jregions, jregion); return true; } static void filter_dimm(struct ndctl_dimm *dimm, struct ndctl_filter_ctx *ctx) { struct list_filter_arg *lfa = ctx->list; struct json_object *jdimm; if (!list.configured && !list.idle && !ndctl_dimm_is_enabled(dimm)) return; if (!lfa->jdimms) { lfa->jdimms = json_object_new_array(); if (!lfa->jdimms) { fail("\n"); return; } if (lfa->jbus) json_object_object_add(lfa->jbus, "dimms", lfa->jdimms); } jdimm = util_dimm_to_json(dimm, lfa->flags); if (!jdimm) { fail("\n"); return; } if (list.health) { struct json_object *jhealth; jhealth = util_dimm_health_to_json(dimm); if (jhealth) json_object_object_add(jdimm, "health", jhealth); else if (ndctl_dimm_is_cmd_supported(dimm, ND_CMD_SMART)) { /* * Failed to retrieve health data from a dimm * that otherwise supports smart data retrieval * commands. */ fail("\n"); return; } } /* * Without a bus we are collecting dimms anonymously across the * platform. */ json_object_array_add(lfa->jdimms, jdimm); } static bool filter_bus(struct ndctl_bus *bus, struct ndctl_filter_ctx *ctx) { struct list_filter_arg *lfa = ctx->list; /* * These sub-objects are local to a bus and, if present, have * been added as a child of a parent object on the last * iteration. */ if (lfa->jbuses) { lfa->jdimms = NULL; lfa->jregion = NULL; lfa->jregions = NULL; lfa->jnamespaces = NULL; } if (!list.buses) return true; if (!lfa->jbuses) { lfa->jbuses = json_object_new_array(); if (!lfa->jbuses) { fail("\n"); return false; } } lfa->jbus = util_bus_to_json(bus, lfa->flags); if (!lfa->jbus) { fail("\n"); return false; } json_object_array_add(lfa->jbuses, lfa->jbus); return true; } static int list_display(struct list_filter_arg *lfa) { struct json_object *jnamespaces = lfa->jnamespaces; struct json_object *jregions = lfa->jregions; struct json_object *jdimms = lfa->jdimms; struct json_object *jbuses = lfa->jbuses; if (jbuses) util_display_json_array(stdout, jbuses, lfa->flags); else if ((!!jdimms + !!jregions + !!jnamespaces) > 1) { struct json_object *jplatform = json_object_new_object(); if (!jplatform) { fail("\n"); return -ENOMEM; } if (jdimms) json_object_object_add(jplatform, "dimms", jdimms); if (jregions) json_object_object_add(jplatform, "regions", jregions); if (jnamespaces && !jregions) json_object_object_add(jplatform, "namespaces", jnamespaces); printf("%s\n", json_object_to_json_string_ext(jplatform, JSON_C_TO_STRING_PRETTY)); json_object_put(jplatform); } else if (jdimms) util_display_json_array(stdout, jdimms, lfa->flags); else if (jregions) util_display_json_array(stdout, jregions, lfa->flags); else if (jnamespaces) util_display_json_array(stdout, jnamespaces, lfa->flags); return 0; } static int num_list_flags(void) { return list.buses + list.dimms + list.regions + list.namespaces; } int cmd_list(int argc, const char **argv, struct ndctl_ctx *ctx) { const struct option options[] = { OPT_STRING('b', "bus", ¶m.bus, "bus-id", "filter by bus"), OPT_STRING('r', "region", ¶m.region, "region-id", "filter by region"), OPT_STRING('d', "dimm", ¶m.dimm, "dimm-id", "filter by dimm"), OPT_STRING('n', "namespace", ¶m.namespace, "namespace-id", "filter by namespace id"), OPT_STRING('m', "mode", ¶m.mode, "namespace-mode", "filter by namespace mode"), OPT_STRING('t', "type", ¶m.type, "region-type", "filter by region-type"), OPT_STRING('U', "numa-node", ¶m.numa_node, "numa node", "filter by numa node"), OPT_BOOLEAN('B', "buses", &list.buses, "include bus info"), OPT_BOOLEAN('D', "dimms", &list.dimms, "include dimm info"), OPT_BOOLEAN('F', "firmware", &list.firmware, "include firmware info"), OPT_BOOLEAN('H', "health", &list.health, "include dimm health"), OPT_BOOLEAN('R', "regions", &list.regions, "include region info"), OPT_BOOLEAN('N', "namespaces", &list.namespaces, "include namespace info (default)"), OPT_BOOLEAN('X', "device-dax", &list.dax, "include device-dax info"), OPT_BOOLEAN('C', "capabilities", &list.capabilities, "include region capability info"), OPT_BOOLEAN('i', "idle", &list.idle, "include idle devices"), OPT_BOOLEAN('c', "configured", &list.configured, "include configured namespaces, disabled or not"), OPT_BOOLEAN('M', "media-errors", &list.media_errors, "include media errors"), OPT_BOOLEAN('u', "human", &list.human, "use human friendly number formats "), OPT_INCR('v', "verbose", &list.verbose, "increase output detail"), OPT_END(), }; const char * const u[] = { "ndctl list []", NULL }; bool lint = !!secure_getenv("NDCTL_LIST_LINT"); struct ndctl_filter_ctx fctx = { 0 }; struct list_filter_arg lfa = { 0 }; int i, rc; argc = parse_options(argc, argv, options, u, 0); for (i = 0; i < argc; i++) error("unknown parameter \"%s\"\n", argv[i]); if (argc) usage_with_options(u, options); if (num_list_flags() == 0) { list.buses = !!param.bus; list.regions = !!param.region; list.dimms = !!param.dimm; if (list.dax && !param.mode) param.mode = "dax"; } switch (list.verbose) { default: case 3: list.idle = true; list.firmware = true; list.health = true; list.capabilities = true; case 2: if (!lint) { list.dimms = true; list.buses = true; list.regions = true; } else if (num_list_flags() == 0) { list.dimms = true; list.buses = true; list.regions = true; list.namespaces = true; } case 1: list.media_errors = true; if (!lint) list.namespaces = true; list.dax = true; case 0: break; } if (num_list_flags() == 0) list.namespaces = true; fctx.filter_bus = filter_bus; fctx.filter_dimm = list.dimms ? filter_dimm : NULL; fctx.filter_region = filter_region; fctx.filter_namespace = list.namespaces ? filter_namespace : NULL; fctx.list = &lfa; lfa.flags = listopts_to_flags(); rc = ndctl_filter_walk(ctx, &fctx, ¶m); if (rc) return rc; if (list_display(&lfa) || did_fail) return -ENOMEM; return 0; } ndctl-81/ndctl/load-keys.c000066400000000000000000000121741476737544500156030ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) 2019-2020 Intel Corporation. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "filter.h" #include "keys.h" static struct parameters { const char *key_path; const char *tpm_handle; } param; static const char *key_names[] = {"user", "trusted", "encrypted"}; static struct loadkeys { enum key_type key_type; DIR *dir; int dirfd; } loadkey_ctx; static int load_master_key(struct loadkeys *lk_ctx, const char *keypath) { key_serial_t key; char *blob; int size, rc; char path[PATH_MAX]; enum key_type; rc = sprintf(path, "%s/nvdimm-master.blob", keypath); if (rc < 0) return -errno; if (param.tpm_handle) lk_ctx->key_type = KEY_TRUSTED; else lk_ctx->key_type = KEY_USER; key = keyctl_search(KEY_SPEC_USER_KEYRING, key_names[lk_ctx->key_type], "nvdimm-master", 0); if (key > 0) /* check to see if key already loaded */ return 0; if (key < 0 && errno != ENOKEY) { fprintf(stderr, "keyctl_search() failed: %s\n", strerror(errno)); return -errno; } blob = ndctl_load_key_blob(path, &size, param.tpm_handle, -1, lk_ctx->key_type); if (!blob) return -ENOMEM; key = add_key(key_names[lk_ctx->key_type], "nvdimm-master", blob, size, KEY_SPEC_USER_KEYRING); free(blob); if (key < 0) { fprintf(stderr, "add_key failed: %s\n", strerror(errno)); return -errno; } printf("nvdimm master key loaded.\n"); return 0; } static int load_dimm_keys(struct loadkeys *lk_ctx) { int rc; struct dirent *dent; char *fname = NULL, *id, *blob = NULL; char desc[ND_KEY_DESC_SIZE]; int size, count = 0; key_serial_t key; while ((dent = readdir(lk_ctx->dir)) != NULL) { if (dent->d_type != DT_REG) continue; fname = strdup(dent->d_name); if (!fname) { fprintf(stderr, "Unable to strdup %s\n", dent->d_name); return -ENOMEM; } /* * We want to pick up the second member of the file name * as the nvdimm id. */ id = strtok(fname, "_"); if (!id) { free(fname); continue; } if (strcmp(id, "nvdimm") != 0) { free(fname); continue; } id = strtok(NULL, "_"); if (!id) { free(fname); continue; } blob = ndctl_load_key_blob(dent->d_name, &size, NULL, lk_ctx->dirfd, KEY_ENCRYPTED); if (!blob) { free(fname); continue; } rc = sprintf(desc, "nvdimm:%s", id); if (rc < 0) { free(fname); free(blob); continue; } key = add_key("encrypted", desc, blob, size, KEY_SPEC_USER_KEYRING); if (key < 0) fprintf(stderr, "add_key failed: %s\n", strerror(errno)); else count++; free(fname); free(blob); } printf("%d nvdimm keys loaded\n", count); return 0; } static int check_tpm_handle(struct loadkeys *lk_ctx) { int fd, rc; FILE *fs; char *buf; fd = openat(lk_ctx->dirfd, "tpm.handle", O_RDONLY); if (fd < 0) return -errno; fs = fdopen(fd, "r"); if (!fs) { fprintf(stderr, "Failed to open file stream: %s\n", strerror(errno)); return -errno; } rc = fscanf(fs, "%ms", &buf); if (rc < 0) { rc = -errno; fprintf(stderr, "Failed to read file: %s\n", strerror(errno)); fclose(fs); return rc; } param.tpm_handle = buf; fclose(fs); return 0; } static int load_keys(struct loadkeys *lk_ctx, const char *keypath, const char *tpmhandle) { int rc; rc = chdir(keypath); if (rc < 0) { fprintf(stderr, "Change current work dir to %s failed: %s\n", param.key_path, strerror(errno)); rc = -errno; goto erropen; } lk_ctx->dir = opendir(param.key_path); if (!lk_ctx->dir) { fprintf(stderr, "Unable to open dir %s: %s\n", param.key_path, strerror(errno)); rc = -errno; goto erropen; } lk_ctx->dirfd = open(param.key_path, O_DIRECTORY); if (lk_ctx->dirfd < 0) { fprintf(stderr, "Unable to open dir %s: %s\n", param.key_path, strerror(errno)); rc = -errno; goto erropen; } if (!tpmhandle) { rc = check_tpm_handle(lk_ctx); if (rc < 0) fprintf(stderr, "No TPM handle discovered.\n"); } rc = load_master_key(lk_ctx, param.key_path); if (rc < 0) goto out; rc = load_dimm_keys(lk_ctx); if (rc < 0) goto out; out: close(lk_ctx->dirfd); erropen: closedir(lk_ctx->dir); return rc; } int cmd_load_keys(int argc, const char **argv, struct ndctl_ctx *ctx) { const struct option options[] = { OPT_FILENAME('p', "key-path", ¶m.key_path, "key-path", "override the default key path"), OPT_STRING('t', "tpm-handle", ¶m.tpm_handle, "tpm-handle", "TPM handle for trusted key"), OPT_END(), }; const char *const u[] = { "ndctl load-keys []", NULL }; int i; argc = parse_options(argc, argv, options, u, 0); for (i = 0; i < argc; i++) error("unknown parameter \"%s\"\n", argv[i]); if (argc) usage_with_options(u, options); if (!param.key_path) param.key_path = strdup(NDCTL_KEYS_DIR); return load_keys(&loadkey_ctx, param.key_path, param.tpm_handle); } ndctl-81/ndctl/meson.build000066400000000000000000000025521476737544500157100ustar00rootroot00000000000000ndctl_src = [ 'ndctl.c', 'bus.c', 'create-nfit.c', 'namespace.c', 'check.c', 'region.c', 'dimm.c', '../daxctl/filter.c', 'filter.c', 'list.c', '../daxctl/json.c', 'json.c', 'json-smart.c', 'inject-error.c', 'inject-smart.c', 'monitor.c', ] deps = [ util_dep, ndctl_dep, daxctl_dep, cxl_dep, uuid, kmod, json, versiondep, ] if get_option('keyutils').enabled() ndctl_src += [ 'keys.c', 'load-keys.c', ] deps += keyutils endif if get_option('test').enabled() ndctl_src += [ '../test/libndctl.c', '../test/dsm-fail.c', '../util/sysfs.c', '../test/core.c', 'test.c', ] endif if get_option('destructive').enabled() if get_option('test').disabled() error('\'-D=destructive=enabled\' requires \'-Dtest=enabled\'\n') endif ndctl_src += [ '../test/pmem_namespaces.c', 'bat.c', ] endif if get_option('systemd').enabled() install_data('ndctl-monitor.service', install_dir : systemdunitdir) endif install_data('monitor.conf', install_dir : ndctlconf_dir) install_data('ndctl.conf', install_dir : ndctlconf_dir) install_data('keys.readme', install_dir : ndctlkeys_dir) ndctl_tool = executable('ndctl', ndctl_src, dependencies : deps, install : true, install_dir : rootbindir, include_directories : root_inc, ) install_headers( [ 'libndctl.h', 'ndctl.h' ], subdir : 'ndctl' ) ndctl-81/ndctl/monitor.c000066400000000000000000000411351476737544500154010ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2018, FUJITSU LIMITED. All rights reserved. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define BUF_SIZE 2048 /* reuse the core log helpers for the monitor logger */ #ifndef ENABLE_LOGGING #define ENABLE_LOGGING #endif #ifndef ENABLE_DEBUG #define ENABLE_DEBUG #endif #include #include "filter.h" #include "json.h" static struct monitor { const char *log; const char *configs; const char *dimm_event; bool daemon; bool human; bool verbose; unsigned int poll_timeout; unsigned int event_flags; struct log_ctx ctx; } monitor; struct monitor_dimm { struct ndctl_dimm *dimm; int health_eventfd; unsigned int health; unsigned int event_flags; struct list_node list; }; static struct ndctl_filter_params param; static int did_fail; #define fail(fmt, ...) \ do { \ did_fail = 1; \ dbg(&monitor, "ndctl-%s:%s:%d: " fmt, \ VERSION, __func__, __LINE__, ##__VA_ARGS__); \ } while (0) static struct json_object *dimm_event_to_json(struct monitor_dimm *mdimm) { struct json_object *jevent, *jobj; bool spares_flag, media_temp_flag, ctrl_temp_flag, health_state_flag, unclean_shutdown_flag; jevent = json_object_new_object(); if (!jevent) { fail("\n"); return NULL; } if (monitor.event_flags & ND_EVENT_SPARES_REMAINING) { spares_flag = !!(mdimm->event_flags & ND_EVENT_SPARES_REMAINING); jobj = json_object_new_boolean(spares_flag); if (jobj) json_object_object_add(jevent, "dimm-spares-remaining", jobj); } if (monitor.event_flags & ND_EVENT_MEDIA_TEMPERATURE) { media_temp_flag = !!(mdimm->event_flags & ND_EVENT_MEDIA_TEMPERATURE); jobj = json_object_new_boolean(media_temp_flag); if (jobj) json_object_object_add(jevent, "dimm-media-temperature", jobj); } if (monitor.event_flags & ND_EVENT_CTRL_TEMPERATURE) { ctrl_temp_flag = !!(mdimm->event_flags & ND_EVENT_CTRL_TEMPERATURE); jobj = json_object_new_boolean(ctrl_temp_flag); if (jobj) json_object_object_add(jevent, "dimm-controller-temperature", jobj); } if (monitor.event_flags & ND_EVENT_HEALTH_STATE) { health_state_flag = !!(mdimm->event_flags & ND_EVENT_HEALTH_STATE); jobj = json_object_new_boolean(health_state_flag); if (jobj) json_object_object_add(jevent, "dimm-health-state", jobj); } if (monitor.event_flags & ND_EVENT_UNCLEAN_SHUTDOWN) { unclean_shutdown_flag = !!(mdimm->event_flags & ND_EVENT_UNCLEAN_SHUTDOWN); jobj = json_object_new_boolean(unclean_shutdown_flag); if (jobj) json_object_object_add(jevent, "dimm-unclean-shutdown", jobj); } return jevent; } static int notify_dimm_event(struct monitor_dimm *mdimm) { struct json_object *jmsg, *jdimm, *jobj; struct timespec ts; char timestamp[32]; jmsg = json_object_new_object(); if (!jmsg) { fail("\n"); return -ENOMEM; } clock_gettime(CLOCK_REALTIME, &ts); sprintf(timestamp, "%10ld.%09ld", ts.tv_sec, ts.tv_nsec); jobj = json_object_new_string(timestamp); if (jobj) json_object_object_add(jmsg, "timestamp", jobj); jobj = json_object_new_int(getpid()); if (jobj) json_object_object_add(jmsg, "pid", jobj); jobj = dimm_event_to_json(mdimm); if (jobj) json_object_object_add(jmsg, "event", jobj); jdimm = util_dimm_to_json(mdimm->dimm, 0); if (jdimm) json_object_object_add(jmsg, "dimm", jdimm); jobj = util_dimm_health_to_json(mdimm->dimm); if (jobj) json_object_object_add(jdimm, "health", jobj); if (monitor.human) notice(&monitor, "%s\n", json_object_to_json_string_ext(jmsg, JSON_C_TO_STRING_PRETTY)); else notice(&monitor, "%s\n", json_object_to_json_string_ext(jmsg, JSON_C_TO_STRING_PLAIN)); free(jobj); free(jdimm); free(jmsg); return 0; } static struct monitor_dimm *util_dimm_event_filter(struct monitor_dimm *mdimm, unsigned int event_flags) { unsigned int health; mdimm->event_flags = ndctl_dimm_get_event_flags(mdimm->dimm); if (mdimm->event_flags == UINT_MAX) return NULL; health = ndctl_dimm_get_health(mdimm->dimm); if (health == UINT_MAX) return NULL; if (mdimm->health != health) mdimm->event_flags |= ND_EVENT_HEALTH_STATE; if (mdimm->event_flags & event_flags) return mdimm; return NULL; } static int enable_dimm_supported_threshold_alarms(struct ndctl_dimm *dimm) { unsigned int alarm; int rc = -EOPNOTSUPP; struct ndctl_cmd *st_cmd = NULL, *sst_cmd = NULL; const char *name = ndctl_dimm_get_devname(dimm); st_cmd = ndctl_dimm_cmd_new_smart_threshold(dimm); if (!st_cmd) { err(&monitor, "%s: no smart threshold command support\n", name); goto out; } if (ndctl_cmd_submit_xlat(st_cmd) < 0) { err(&monitor, "%s: smart threshold command failed\n", name); goto out; } sst_cmd = ndctl_dimm_cmd_new_smart_set_threshold(st_cmd); if (!sst_cmd) { err(&monitor, "%s: no smart set threshold command support\n", name); goto out; } alarm = ndctl_cmd_smart_threshold_get_alarm_control(st_cmd); if (monitor.event_flags & ND_EVENT_SPARES_REMAINING) alarm |= ND_SMART_SPARE_TRIP; if (monitor.event_flags & ND_EVENT_MEDIA_TEMPERATURE) alarm |= ND_SMART_TEMP_TRIP; if (monitor.event_flags & ND_EVENT_CTRL_TEMPERATURE) alarm |= ND_SMART_CTEMP_TRIP; ndctl_cmd_smart_threshold_set_alarm_control(sst_cmd, alarm); rc = ndctl_cmd_submit_xlat(sst_cmd); if (rc < 0) { err(&monitor, "%s: smart set threshold command failed\n", name); goto out; } out: ndctl_cmd_unref(sst_cmd); ndctl_cmd_unref(st_cmd); return rc; } static bool filter_region(struct ndctl_region *region, struct ndctl_filter_ctx *fctx) { return true; } static void filter_dimm(struct ndctl_dimm *dimm, struct ndctl_filter_ctx *fctx) { struct monitor_dimm *mdimm; struct monitor_filter_arg *mfa = fctx->monitor; const char *name = ndctl_dimm_get_devname(dimm); if (!ndctl_dimm_is_cmd_supported(dimm, ND_CMD_SMART)) { err(&monitor, "%s: no smart support\n", name); return; } if (!ndctl_dimm_is_cmd_supported(dimm, ND_CMD_SMART_THRESHOLD)) { dbg(&monitor, "%s: no smart threshold support\n", name); } else if (!ndctl_dimm_is_flag_supported(dimm, ND_SMART_ALARM_VALID)) { err(&monitor, "%s: smart alarm invalid\n", name); return; } else if (enable_dimm_supported_threshold_alarms(dimm)) { err(&monitor, "%s: enable supported threshold alarms failed\n", name); return; } mdimm = calloc(1, sizeof(struct monitor_dimm)); if (!mdimm) { err(&monitor, "%s: calloc for monitor dimm failed\n", name); return; } mdimm->dimm = dimm; mdimm->health_eventfd = ndctl_dimm_get_health_eventfd(dimm); mdimm->health = ndctl_dimm_get_health(dimm); mdimm->event_flags = ndctl_dimm_get_event_flags(dimm); if (mdimm->event_flags && util_dimm_event_filter(mdimm, monitor.event_flags)) { if (notify_dimm_event(mdimm)) { err(&monitor, "%s: notify dimm event failed\n", name); free(mdimm); return; } } list_add_tail(&mfa->dimms, &mdimm->list); if (mdimm->health_eventfd > mfa->maxfd_dimm) mfa->maxfd_dimm = mdimm->health_eventfd; mfa->num_dimm++; return; } static bool filter_bus(struct ndctl_bus *bus, struct ndctl_filter_ctx *fctx) { return true; } static int monitor_event(struct ndctl_ctx *ctx, struct monitor_filter_arg *mfa) { struct epoll_event ev, *events; int nfds, epollfd, i, rc = 0, polltimeout = -1; struct monitor_dimm *mdimm; char buf; /* last time a full poll happened */ struct timespec fullpoll_ts, ts; if (monitor.poll_timeout) polltimeout = monitor.poll_timeout * 1000; events = calloc(mfa->num_dimm, sizeof(struct epoll_event)); if (!events) { err(&monitor, "malloc for events error\n"); return -ENOMEM; } epollfd = epoll_create1(0); if (epollfd == -1) { err(&monitor, "epoll_create1 error\n"); rc = -errno; goto out; } list_for_each(&mfa->dimms, mdimm, list) { memset(&ev, 0, sizeof(ev)); rc = pread(mdimm->health_eventfd, &buf, sizeof(buf), 0); if (rc < 0) { err(&monitor, "pread error\n"); rc = -errno; goto out; } ev.data.ptr = mdimm; if (epoll_ctl(epollfd, EPOLL_CTL_ADD, mdimm->health_eventfd, &ev) != 0) { err(&monitor, "epoll_ctl error\n"); rc = -errno; goto out; } } clock_gettime(CLOCK_BOOTTIME, &fullpoll_ts); while (1) { did_fail = 0; nfds = epoll_wait(epollfd, events, mfa->num_dimm, polltimeout); if (nfds < 0 && errno != EINTR) { err(&monitor, "epoll_wait error: (%s)\n", strerror(errno)); rc = -errno; goto out; } /* If needed force a full poll of dimm health */ clock_gettime(CLOCK_BOOTTIME, &ts); if ((fullpoll_ts.tv_sec - ts.tv_sec) > monitor.poll_timeout) { nfds = 0; dbg(&monitor, "forcing a full poll\n"); } /* If we timed out then fill events array with all dimms */ if (nfds == 0) { list_for_each(&mfa->dimms, mdimm, list) events[nfds++].data.ptr = mdimm; fullpoll_ts = ts; } for (i = 0; i < nfds; i++) { mdimm = events[i].data.ptr; if (util_dimm_event_filter(mdimm, monitor.event_flags)) { rc = notify_dimm_event(mdimm); if (rc) { err(&monitor, "%s: notify dimm event failed\n", ndctl_dimm_get_devname(mdimm->dimm)); did_fail = 1; goto out; } } rc = pread(mdimm->health_eventfd, &buf, sizeof(buf), 0); if (rc < 0) { err(&monitor, "pread error\n"); rc = -errno; goto out; } } if (did_fail) return 1; } out: free(events); return rc; } static void monitor_enable_all_events(struct monitor *_monitor) { _monitor->event_flags = ND_EVENT_SPARES_REMAINING | ND_EVENT_MEDIA_TEMPERATURE | ND_EVENT_CTRL_TEMPERATURE | ND_EVENT_HEALTH_STATE | ND_EVENT_UNCLEAN_SHUTDOWN; } static int parse_monitor_event(struct monitor *_monitor, struct ndctl_ctx *ctx) { char *dimm_event, *save; const char *event; int rc = 0; if (!_monitor->dimm_event) { monitor_enable_all_events(_monitor); return 0;; } dimm_event = strdup(_monitor->dimm_event); if (!dimm_event) return -ENOMEM; for (event = strtok_r(dimm_event, " ", &save); event; event = strtok_r(NULL, " ", &save)) { if (strcmp(event, "all") == 0) { monitor_enable_all_events(_monitor); goto out; } if (strcmp(event, "dimm-spares-remaining") == 0) _monitor->event_flags |= ND_EVENT_SPARES_REMAINING; else if (strcmp(event, "dimm-media-temperature") == 0) _monitor->event_flags |= ND_EVENT_MEDIA_TEMPERATURE; else if (strcmp(event, "dimm-controller-temperature") == 0) _monitor->event_flags |= ND_EVENT_CTRL_TEMPERATURE; else if (strcmp(event, "dimm-health-state") == 0) _monitor->event_flags |= ND_EVENT_HEALTH_STATE; else if (strcmp(event, "dimm-unclean-shutdown") == 0) _monitor->event_flags |= ND_EVENT_UNCLEAN_SHUTDOWN; else { err(&monitor, "no dimm-event named %s\n", event); rc = -EINVAL; goto out; } } out: free(dimm_event); return rc; } static void set_monitor_conf(const char **arg, char *key, char *val, char *ident) { struct strbuf value = STRBUF_INIT; size_t arg_len = *arg ? strlen(*arg) : 0; if (!ident || !key || (strcmp(ident, key) != 0)) return; if (arg_len) { strbuf_add(&value, *arg, arg_len); strbuf_addstr(&value, " "); } strbuf_addstr(&value, val); *arg = strbuf_detach(&value, NULL); } static int parse_monitor_config(const struct config *configs, const char *config_file) { FILE *f; size_t len = 0; int line = 0, rc = 0; char *buf = NULL, *seek, *value; buf = malloc(BUF_SIZE); if (!buf) { fail("malloc read config-file buf error\n"); return -ENOMEM; } seek = buf; f = fopen(config_file, "r"); if (!f) { err(&monitor, "%s cannot be opened\n", config_file); rc = -errno; goto out; } while (fgets(seek, BUF_SIZE, f)) { value = NULL; line++; while (isspace(*seek)) seek++; if (*seek == '#' || *seek == '\0') continue; value = strchr(seek, '='); if (!value) { fail("config-file syntax error, skip line[%i]\n", line); continue; } value[0] = '\0'; value++; while (isspace(value[0])) value++; len = strlen(seek); if (len == 0) continue; while (isspace(seek[len-1])) len--; seek[len] = '\0'; len = strlen(value); if (len == 0) continue; while (isspace(value[len-1])) len--; value[len] = '\0'; if (len == 0) continue; set_monitor_conf(¶m.bus, "bus", value, seek); set_monitor_conf(¶m.dimm, "dimm", value, seek); set_monitor_conf(¶m.region, "region", value, seek); set_monitor_conf(¶m.namespace, "namespace", value, seek); set_monitor_conf(&monitor.dimm_event, "dimm-event", value, seek); if (!monitor.log) set_monitor_conf(&monitor.log, "log", value, seek); } fclose(f); out: free(buf); return rc; } int cmd_monitor(int argc, const char **argv, struct ndctl_ctx *ctx) { const struct option options[] = { OPT_STRING('b', "bus", ¶m.bus, "bus-id", "filter by bus"), OPT_STRING('r', "region", ¶m.region, "region-id", "filter by region"), OPT_STRING('d', "dimm", ¶m.dimm, "dimm-id", "filter by dimm"), OPT_STRING('n', "namespace", ¶m.namespace, "namespace-id", "filter by namespace id"), OPT_STRING('D', "dimm-event", &monitor.dimm_event, "name of event type", "filter by DIMM event type"), OPT_FILENAME('l', "log", &monitor.log, " | syslog | standard", "where to output the monitor's notification"), OPT_STRING('c', "config-file", &monitor.configs, "config-file", "override default configs"), OPT_BOOLEAN('\0', "daemon", &monitor.daemon, "run ndctl monitor as a daemon"), OPT_BOOLEAN('u', "human", &monitor.human, "use human friendly output formats"), OPT_BOOLEAN('v', "verbose", &monitor.verbose, "emit extra debug messages to log"), OPT_UINTEGER('p', "poll", &monitor.poll_timeout, "poll and report events/status every seconds"), OPT_END(), }; const char * const u[] = { "ndctl monitor []", NULL }; struct config configs[] = { CONF_MONITOR(NDCTL_CONF_FILE, parse_monitor_config), CONF_STR("core:bus", ¶m.bus, NULL), CONF_STR("core:region", ¶m.region, NULL), CONF_STR("core:dimm", ¶m.dimm, NULL), CONF_STR("core:namespace", ¶m.namespace, NULL), CONF_STR("monitor:bus", ¶m.bus, NULL), CONF_STR("monitor:region", ¶m.region, NULL), CONF_STR("monitor:dimm", ¶m.dimm, NULL), CONF_STR("monitor:namespace", ¶m.namespace, NULL), CONF_STR("monitor:dimm-event", &monitor.dimm_event, NULL), CONF_END(), }; const char *prefix = "./", *ndctl_configs; struct ndctl_filter_ctx fctx = { 0 }; struct monitor_filter_arg mfa = { 0 }; int i, rc = 0; struct stat st; char *path = NULL; argc = parse_options_prefix(argc, argv, prefix, options, u, 0); for (i = 0; i < argc; i++) { error("unknown parameter \"%s\"\n", argv[i]); } if (argc) usage_with_options(u, options); log_init(&monitor.ctx, "ndctl/monitor", "NDCTL_MONITOR_LOG"); monitor.ctx.log_fn = log_standard; if (monitor.verbose) monitor.ctx.log_priority = LOG_DEBUG; else monitor.ctx.log_priority = LOG_INFO; ndctl_configs = ndctl_get_config_path(ctx); if (!monitor.configs && ndctl_configs) { rc = asprintf(&path, "%s/monitor.conf", ndctl_configs); if (rc < 0) goto out; if (stat(path, &st) == 0) monitor.configs = path; } if (monitor.configs) { configs[0].key = monitor.configs; rc = parse_configs_prefix(monitor.configs, prefix, configs); if (rc) goto out; } if (monitor.log) { if (strncmp(monitor.log, "./", 2) != 0) fix_filename(prefix, (const char **)&monitor.log); if (strcmp(monitor.log, "./syslog") == 0) monitor.ctx.log_fn = log_syslog; else if (strcmp(monitor.log, "./standard") == 0) monitor.ctx.log_fn = log_standard; else { monitor.ctx.log_file = fopen(monitor.log, "a+"); if (!monitor.ctx.log_file) { error("open %s failed\n", monitor.log); rc = -errno; goto out; } monitor.ctx.log_fn = log_file; } } if (monitor.daemon) { if (!monitor.log || strncmp(monitor.log, "./", 2) == 0) monitor.ctx.log_fn = log_syslog; if (daemon(0, 0) != 0) { err(&monitor, "daemon start failed\n"); goto out; } info(&monitor, "ndctl monitor daemon started\n"); } if (parse_monitor_event(&monitor, ctx)) goto out; fctx.filter_bus = filter_bus; fctx.filter_dimm = filter_dimm; fctx.filter_region = filter_region; fctx.filter_namespace = NULL; fctx.arg = &mfa; list_head_init(&mfa.dimms); mfa.num_dimm = 0; mfa.maxfd_dimm = -1; mfa.flags = 0; rc = ndctl_filter_walk(ctx, &fctx, ¶m); if (rc) goto out; if (!mfa.num_dimm) { info(&monitor, "no dimms to monitor, exiting\n"); if (!monitor.daemon) rc = -ENXIO; goto out; } rc = monitor_event(ctx, &mfa); out: if (monitor.ctx.log_file) fclose(monitor.ctx.log_file); if (path) free(path); return rc; } ndctl-81/ndctl/monitor.conf000066400000000000000000000036421476737544500161050ustar00rootroot00000000000000[monitor] # The values in [monitor] section work for ndctl monitor. # You can change the configuration of ndctl monitor by editing this # file or use [--config-file=] option to override this one. # The changed value will work after restart ndctl monitor service. # In this file, lines starting with a hash (#) are comments. # The configurations should follow = style. # Multiple space-separated values are allowed, but except the following # characters: : ? / \ % " ' $ & ! * { } [ ] ( ) = < > @ # The objects to monitor are filtered via dimm's name by setting key "dimm". # If this value is different from the value of [--dimm=] option, # both of the values will work. # dimm = all # The objects to monitor are filtered via its parent bus by setting key "bus". # If this value is different from the value of [--bus=] option, # both of the values will work. # bus = all # The objects to monitor are filtered via region by setting key "region". # If this value is different from the value of [--region=] option, # both of the values will work. # region = all # The objects to monitor are filtered via namespace by setting key "namespace". # If this value is different from the value of [--namespace=] option, # both of the values will work. # namespace = all # The DIMM events to monitor are filtered via event type by setting key # "dimm-event". If this value is different from the value of # [--dimm-event=] option, both of the values will work. # dimm-event = all # Users can choose to output the notifications to syslog (log=syslog), # to standard output (log=standard) or to write into a special file (log=) # by setting key "log". If this value is in conflict with the value of # [--log=] option, this value will be ignored. # Note: Setting value to "standard" or relative path for will not work # when running monitor as a daemon. # log = /var/log/ndctl/monitor.log ndctl-81/ndctl/namespace.c000066400000000000000000001763121476737544500156540ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2015-2020 Intel Corporation. All rights reserved. #include #include #include #include #include #include #include #include #include "action.h" #include "namespace.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "filter.h" #include "json.h" static bool verbose; static bool force; static bool repair; static bool logfix; static bool scrub; static struct parameters { bool do_scan; bool mode_default; bool autolabel; bool greedy; bool verify; bool autorecover; bool human; bool json; bool std_out; const char *bus; const char *map; const char *type; const char *uuid; const char *name; const char *size; const char *mode; const char *region; const char *reconfig; const char *sector_size; const char *align; const char *offset; const char *outfile; const char *infile; const char *parent_uuid; } param = { .autolabel = true, .autorecover = true, }; const char *cmd_name = "namespace"; void builtin_xaction_namespace_reset(void) { /* * Initialize parameter data for the unit test case where * multiple calls to cmd__namespace() are made without * an intervening exit(). */ verbose = false; force = false; memset(¶m, 0, sizeof(param)); } #define NSLABEL_NAME_LEN 64 struct parsed_parameters { enum ndctl_pfn_loc loc; uuid_t uuid; char name[NSLABEL_NAME_LEN]; enum ndctl_namespace_mode mode; unsigned long long size; unsigned long sector_size; unsigned long align; bool autolabel; bool autorecover; }; #define pr_verbose(fmt, ...) \ ({if (verbose) { \ fprintf(stderr, fmt, ##__VA_ARGS__); \ } else { \ do { } while (0); \ }}) #define debug(fmt, ...) \ ({if (verbose) { \ fprintf(stderr, "%s:%d: " fmt, __func__, __LINE__, ##__VA_ARGS__); \ } else { \ do { } while (0); \ }}) static int err_count; #define err(fmt, ...) \ ({ err_count++; error("%s: " fmt, cmd_name, ##__VA_ARGS__); }) #define BASE_OPTIONS() \ OPT_STRING('b', "bus", ¶m.bus, "bus-id", \ "limit namespace to a bus with an id or provider of "), \ OPT_STRING('r', "region", ¶m.region, "region-id", \ "limit namespace to a region with an id or name of "), \ OPT_BOOLEAN('v', "verbose", &verbose, "emit extra debug messages to stderr") #define CREATE_OPTIONS() \ OPT_STRING('e', "reconfig", ¶m.reconfig, "reconfig namespace", \ "reconfigure existing namespace"), \ OPT_STRING('u', "uuid", ¶m.uuid, "uuid", \ "specify the uuid for the namespace (default: autogenerate)"), \ OPT_STRING('n', "name", ¶m.name, "name", \ "specify an optional free form name for the namespace"), \ OPT_STRING('s', "size", ¶m.size, "size", \ "specify the namespace size in bytes (default: available capacity)"), \ OPT_STRING('m', "mode", ¶m.mode, "operation-mode", \ "specify a mode for the namespace, 'sector', 'fsdax', 'devdax' or 'raw'"), \ OPT_STRING('M', "map", ¶m.map, "memmap-location", \ "specify 'mem' or 'dev' for the location of the memmap"), \ OPT_STRING('l', "sector-size", ¶m.sector_size, "lba-size", \ "specify the logical sector size in bytes"), \ OPT_STRING('t', "type", ¶m.type, "type", \ "specify the type of namespace to create 'pmem' or 'blk'"), \ OPT_STRING('a', "align", ¶m.align, "align", \ "specify the namespace alignment in bytes (default: 2M)"), \ OPT_BOOLEAN('f', "force", &force, "reconfigure namespace even if currently active"), \ OPT_BOOLEAN('L', "autolabel", ¶m.autolabel, "automatically initialize labels"), \ OPT_BOOLEAN('c', "continue", ¶m.greedy, \ "continue creating namespaces as long as the filter criteria are met"), \ OPT_BOOLEAN('R', "autorecover", ¶m.autorecover, "automatically cleanup on failure") #define CHECK_OPTIONS() \ OPT_BOOLEAN('R', "repair", &repair, "perform metadata repairs"), \ OPT_BOOLEAN('L', "rewrite-log", &logfix, "regenerate the log"), \ OPT_BOOLEAN('f', "force", &force, "check namespace even if currently active") #define CLEAR_OPTIONS() \ OPT_BOOLEAN('s', "scrub", &scrub, "run a scrub to find latent errors") #define READ_INFOBLOCK_OPTIONS() \ OPT_FILENAME('o', "output", ¶m.outfile, "output-file", \ "filename to write infoblock contents"), \ OPT_FILENAME('i', "input", ¶m.infile, "input-file", \ "filename to read infoblock instead of a namespace"), \ OPT_BOOLEAN('V', "verify", ¶m.verify, \ "validate parent uuid, and infoblock checksum"), \ OPT_BOOLEAN('j', "json", ¶m.json, "parse label data into json"), \ OPT_BOOLEAN('u', "human", ¶m.human, "use human friendly number formats (implies --json)") #define WRITE_INFOBLOCK_OPTIONS() \ OPT_FILENAME('o', "output", ¶m.outfile, "output-file", \ "filename to write infoblock contents"), \ OPT_BOOLEAN('c', "stdout", ¶m.std_out, \ "write the infoblock data to stdout"), \ OPT_STRING('m', "mode", ¶m.mode, "operation-mode", \ "specify the infoblock mode, 'fsdax' or 'devdax' (default 'fsdax')"), \ OPT_STRING('s', "size", ¶m.size, "size", \ "override the image size to instantiate the infoblock"), \ OPT_STRING('a', "align", ¶m.align, "align", \ "specify the expected physical alignment"), \ OPT_STRING('u', "uuid", ¶m.uuid, "uuid", \ "specify the uuid for the infoblock (default: autogenerate)"), \ OPT_STRING('M', "map", ¶m.map, "memmap-location", \ "specify 'mem' or 'dev' for the location of the memmap"), \ OPT_STRING('p', "parent-uuid", ¶m.parent_uuid, "parent-uuid", \ "specify the parent namespace uuid for the infoblock (default: 0)"), \ OPT_STRING('O', "offset", ¶m.offset, "offset", \ "EXPERT/DEBUG only: enable namespace inner alignment padding") static const struct option base_options[] = { BASE_OPTIONS(), OPT_END(), }; static const struct option destroy_options[] = { BASE_OPTIONS(), OPT_BOOLEAN('f', "force", &force, "destroy namespace even if currently active"), OPT_END(), }; static const struct option create_options[] = { BASE_OPTIONS(), CREATE_OPTIONS(), OPT_END(), }; static const struct option check_options[] = { BASE_OPTIONS(), CHECK_OPTIONS(), OPT_END(), }; static const struct option clear_options[] = { BASE_OPTIONS(), CLEAR_OPTIONS(), OPT_END(), }; static const struct option read_infoblock_options[] = { BASE_OPTIONS(), READ_INFOBLOCK_OPTIONS(), OPT_END(), }; static const struct option write_infoblock_options[] = { BASE_OPTIONS(), WRITE_INFOBLOCK_OPTIONS(), OPT_END(), }; static int set_defaults(enum device_action action) { uuid_t uuid; int rc = 0; if (param.type) { if (strcmp(param.type, "pmem") == 0) /* pass */; else if (strcmp(param.type, "blk") == 0) /* pass */; else { error("invalid type '%s', must be either 'pmem' or 'blk'\n", param.type); rc = -EINVAL; } } else if (!param.reconfig && action == ACTION_CREATE) param.type = "pmem"; if (param.mode) { enum ndctl_namespace_mode mode = util_nsmode(param.mode); switch (mode) { case NDCTL_NS_MODE_UNKNOWN: error("invalid mode '%s'\n", param.mode); rc = -EINVAL; break; case NDCTL_NS_MODE_FSDAX: case NDCTL_NS_MODE_DEVDAX: break; default: if (action == ACTION_WRITE_INFOBLOCK) { error("unsupported mode '%s'\n", param.mode); rc = -EINVAL; } break; } } else if (action == ACTION_WRITE_INFOBLOCK) { param.mode = "fsdax"; } else if (!param.reconfig && param.type) { if (strcmp(param.type, "pmem") == 0) param.mode = "fsdax"; else param.mode = "sector"; param.mode_default = true; } if (param.map) { if (strcmp(param.map, "mem") == 0) /* pass */; else if (strcmp(param.map, "dev") == 0) /* pass */; else { error("invalid map location '%s'\n", param.map); rc = -EINVAL; } if (!param.reconfig && param.mode && strcmp(param.mode, "fsdax") != 0 && strcmp(param.mode, "devdax") != 0) { error("--map only valid for an devdax mode pmem namespace\n"); rc = -EINVAL; } } else if (!param.reconfig) param.map = "dev"; /* check for incompatible mode and type combinations */ if (param.type && param.mode && strcmp(param.type, "blk") == 0 && (strcmp(param.mode, "fsdax") == 0 || strcmp(param.mode, "devdax") == 0)) { error("only 'pmem' namespaces support dax operation\n"); rc = -ENXIO; } if (param.size && parse_size64(param.size) == ULLONG_MAX) { error("failed to parse namespace size '%s'\n", param.size); rc = -EINVAL; } if (param.offset && parse_size64(param.offset) == ULLONG_MAX) { error("failed to parse physical offset'%s'\n", param.offset); rc = -EINVAL; } if (param.align) { unsigned long long align = parse_size64(param.align); if (align == ULLONG_MAX) { error("failed to parse namespace alignment '%s'\n", param.align); rc = -EINVAL; } else if (!is_power_of_2(align) || align < (unsigned long long) sysconf(_SC_PAGE_SIZE)) { error("align must be a power-of-2 greater than %ld\n", sysconf(_SC_PAGE_SIZE)); rc = -EINVAL; } } if (param.size) { unsigned long long size = parse_size64(param.size); if (size == ULLONG_MAX) { error("failed to parse namespace size '%s'\n", param.size); rc = -EINVAL; } } if (param.uuid) { if (uuid_parse(param.uuid, uuid)) { error("failed to parse uuid: '%s'\n", param.uuid); rc = -EINVAL; } } if (param.parent_uuid) { if (uuid_parse(param.parent_uuid, uuid)) { error("failed to parse uuid: '%s'\n", param.parent_uuid); rc = -EINVAL; } } if (param.sector_size) { if (parse_size64(param.sector_size) == ULLONG_MAX) { error("invalid sector size: %s\n", param.sector_size); rc = -EINVAL; } } else if (((param.type && strcmp(param.type, "blk") == 0) || util_nsmode(param.mode) == NDCTL_NS_MODE_SECTOR)) { /* default sector size for blk-type or safe-mode */ param.sector_size = "4096"; } return rc; } /* * parse_namespace_options - basic parsing sanity checks before we start * looking at actual namespace devices and available resources. */ static const char *parse_namespace_options(int argc, const char **argv, enum device_action action, const struct option *options, char *xable_usage) { const char * const u[] = { xable_usage, NULL }; int i, rc = 0; param.do_scan = argc == 1; argc = parse_options(argc, argv, options, u, 0); rc = set_defaults(action); if (argc == 0 && action != ACTION_CREATE) { char *action_string; switch (action) { case ACTION_ENABLE: action_string = "enable"; break; case ACTION_DISABLE: action_string = "disable"; break; case ACTION_DESTROY: action_string = "destroy"; break; case ACTION_CHECK: action_string = "check"; break; case ACTION_CLEAR: action_string = "clear errors for"; break; case ACTION_READ_INFOBLOCK: action_string = "read-infoblock"; break; case ACTION_WRITE_INFOBLOCK: action_string = "write-infoblock"; break; default: action_string = "<>"; break; } if ((action != ACTION_READ_INFOBLOCK && action != ACTION_WRITE_INFOBLOCK) || (action == ACTION_WRITE_INFOBLOCK && !param.outfile && !param.std_out)) { error("specify a namespace to %s, or \"all\"\n", action_string); rc = -EINVAL; } } for (i = action == ACTION_CREATE ? 0 : 1; i < argc; i++) { error("unknown extra parameter \"%s\"\n", argv[i]); rc = -EINVAL; } if (action == ACTION_READ_INFOBLOCK && param.infile && argc) { error("specify a namespace, or --input, not both\n"); rc = -EINVAL; } if (action == ACTION_WRITE_INFOBLOCK && (param.outfile || param.std_out) && argc) { error("specify only one of a namespace filter, --output, or --stdout\n"); rc = -EINVAL; } if (action == ACTION_WRITE_INFOBLOCK && param.std_out && !param.size) { error("--size required with --stdout\n"); rc = -EINVAL; } if (rc) { usage_with_options(u, options); return NULL; /* we won't return from usage_with_options() */ } if (action == ACTION_READ_INFOBLOCK && !param.infile && !argc) return NULL; return action == ACTION_CREATE ? param.reconfig : argv[0]; } #define try(prefix, op, dev, p) \ do { \ int __rc = prefix##_##op(dev, p); \ if (__rc) { \ err("%s: " #op " failed: %s\n", \ prefix##_get_devname(dev), \ strerror(abs(__rc))); \ return __rc; \ } \ } while (0) static bool do_setup_pfn(struct ndctl_namespace *ndns, struct parsed_parameters *p) { if (p->mode != NDCTL_NS_MODE_FSDAX) return false; /* * Dynamically allocated namespaces always require a pfn * instance, and a pfn device is required to place the memmap * array in device memory. */ if (!ndns || ndctl_namespace_get_mode(ndns) != NDCTL_NS_MODE_FSDAX || p->loc == NDCTL_PFN_LOC_PMEM) return true; return false; } static int check_dax_align(struct ndctl_namespace *ndns) { unsigned long long resource = ndctl_namespace_get_resource(ndns); const char *devname = ndctl_namespace_get_devname(ndns); if (resource == ULLONG_MAX) { warning("%s unable to validate alignment\n", devname); return 0; } if (IS_ALIGNED(resource, SZ_16M) || force) return 0; error("%s misaligned to 16M, adjust region alignment and retry\n", devname); return -EINVAL; } static int setup_namespace(struct ndctl_region *region, struct ndctl_namespace *ndns, struct parsed_parameters *p) { uuid_t uuid; int rc; if (ndctl_namespace_get_type(ndns) != ND_DEVICE_NAMESPACE_IO) { try(ndctl_namespace, set_uuid, ndns, p->uuid); try(ndctl_namespace, set_alt_name, ndns, p->name); try(ndctl_namespace, set_size, ndns, p->size); } if (p->sector_size && p->sector_size < UINT_MAX) { int i, num = ndctl_namespace_get_num_sector_sizes(ndns); /* * With autolabel support we need to recheck if the * namespace gained sector_size support late in * namespace_reconfig(). */ for (i = 0; i < num; i++) if (ndctl_namespace_get_supported_sector_size(ndns, i) == p->sector_size) break; if (i < num) try(ndctl_namespace, set_sector_size, ndns, p->sector_size); else if (p->mode == NDCTL_NS_MODE_SECTOR) /* pass, the btt sector_size will override */; else if (p->sector_size != 512) { error("%s: sector_size: %ld not supported\n", ndctl_namespace_get_devname(ndns), p->sector_size); return -EINVAL; } } uuid_generate(uuid); /* * Note, this call to ndctl_namespace_set_mode() is not error * checked since kernels older than 4.13 do not support this * property of namespaces and it is an opportunistic enforcement * mechanism. */ ndctl_namespace_set_enforce_mode(ndns, p->mode); if (do_setup_pfn(ndns, p)) { struct ndctl_pfn *pfn = ndctl_region_get_pfn_seed(region); if (!pfn) return -ENXIO; rc = check_dax_align(ndns); if (rc) return rc; try(ndctl_pfn, set_uuid, pfn, uuid); try(ndctl_pfn, set_location, pfn, p->loc); if (ndctl_pfn_has_align(pfn)) try(ndctl_pfn, set_align, pfn, p->align); try(ndctl_pfn, set_namespace, pfn, ndns); rc = ndctl_pfn_enable(pfn); if (rc && p->autorecover) ndctl_pfn_set_namespace(pfn, NULL); } else if (p->mode == NDCTL_NS_MODE_DEVDAX) { struct ndctl_dax *dax = ndctl_region_get_dax_seed(region); if (!dax) return -ENXIO; rc = check_dax_align(ndns); if (rc) return rc; try(ndctl_dax, set_uuid, dax, uuid); try(ndctl_dax, set_location, dax, p->loc); /* device-dax assumes 'align' attribute present */ try(ndctl_dax, set_align, dax, p->align); try(ndctl_dax, set_namespace, dax, ndns); rc = ndctl_dax_enable(dax); if (rc && p->autorecover) ndctl_dax_set_namespace(dax, NULL); } else if (p->mode == NDCTL_NS_MODE_SECTOR) { struct ndctl_btt *btt = ndctl_region_get_btt_seed(region); if (!btt) return -ENXIO; /* * Handle the case of btt on a pmem namespace where the * pmem kernel support is pre-v1.2 namespace labels * support (does not support sector size settings). */ if (p->sector_size == UINT_MAX) p->sector_size = 4096; try(ndctl_btt, set_uuid, btt, uuid); try(ndctl_btt, set_sector_size, btt, p->sector_size); try(ndctl_btt, set_namespace, btt, ndns); rc = ndctl_btt_enable(btt); } else rc = ndctl_namespace_enable(ndns); if (rc) { error("%s: failed to enable\n", ndctl_namespace_get_devname(ndns)); } else { unsigned long flags = UTIL_JSON_DAX | UTIL_JSON_DAX_DEVS; struct json_object *jndns; if (isatty(1)) flags |= UTIL_JSON_HUMAN; jndns = util_namespace_to_json(ndns, flags); if (jndns) printf("%s\n", json_object_to_json_string_ext(jndns, JSON_C_TO_STRING_PRETTY)); } return rc; } static int validate_available_capacity(struct ndctl_region *region, struct parsed_parameters *p) { unsigned long long available; if (ndctl_region_get_nstype(region) == ND_DEVICE_NAMESPACE_IO) available = ndctl_region_get_size(region); else { available = ndctl_region_get_max_available_extent(region); if (available == ULLONG_MAX) available = ndctl_region_get_available_size(region); } if (!available || p->size > available) { debug("%s: insufficient capacity size: %llx avail: %llx\n", ndctl_region_get_devname(region), p->size, available); return -EAGAIN; } if (p->size == 0) p->size = available; return 0; } /* * validate_namespace_options - init parameters for setup_namespace * @region: parent of the namespace to create / reconfigure * @ndns: specified when we are reconfiguring, NULL otherwise * @p: parameters to fill * * parse_namespace_options() will have already done basic verification * of the parameters and set defaults in the !reconfigure case. When * reconfiguring fill in any unset options with defaults from the * namespace itself. * * Given that parse_namespace_options() runs before we have identified * the target namespace we need to do basic sanity checks here for * pmem-only attributes specified for blk namespace and vice versa. */ static int validate_namespace_options(struct ndctl_region *region, struct ndctl_namespace *ndns, struct parsed_parameters *p) { const char *region_name = ndctl_region_get_devname(region); unsigned long long size_align, units = 1, resource; struct ndctl_pfn *pfn = NULL; struct ndctl_dax *dax = NULL; unsigned long region_align; bool default_size = false; unsigned int ways; int rc = 0; memset(p, 0, sizeof(*p)); if (!ndctl_region_is_enabled(region)) { debug("%s: disabled, skipping...\n", region_name); return -EAGAIN; } if (param.size) p->size = __parse_size64(param.size, &units); else if (ndns) p->size = ndctl_namespace_get_size(ndns); else default_size = true; /* * Validate available capacity in the create case, in the * reconfigure case the capacity is already allocated. A default * size will be established from available capacity. */ if (!ndns) { rc = validate_available_capacity(region, p); if (rc) return rc; } /* * Block attempts to set a custom size on legacy (label-less) * namespaces */ if (ndctl_region_get_nstype(region) == ND_DEVICE_NAMESPACE_IO && p->size != ndctl_region_get_size(region)) { error("Legacy / label-less namespaces do not support sub-dividing a region\n"); error("Retry without -s/--size=\n"); return -EINVAL; } if (param.uuid) { if (uuid_parse(param.uuid, p->uuid) != 0) { err("%s: invalid uuid\n", __func__); return -EINVAL; } } else uuid_generate(p->uuid); if (param.name) rc = snprintf(p->name, sizeof(p->name), "%s", param.name); else if (ndns) rc = snprintf(p->name, sizeof(p->name), "%s", ndctl_namespace_get_alt_name(ndns)); if (rc >= (int) sizeof(p->name)) { err("%s: alt name overflow\n", __func__); return -EINVAL; } if (param.mode) { p->mode = util_nsmode(param.mode); if (ndctl_region_get_type(region) != ND_DEVICE_REGION_PMEM && (p->mode == NDCTL_NS_MODE_FSDAX || p->mode == NDCTL_NS_MODE_DEVDAX)) { err("blk %s does not support %s mode\n", region_name, util_nsmode_name(p->mode)); return -EAGAIN; } } else if (ndns) p->mode = ndctl_namespace_get_mode(ndns); if (p->mode == NDCTL_NS_MODE_FSDAX) { pfn = ndctl_region_get_pfn_seed(region); if (!pfn && param.mode_default) { err("%s fsdax mode not available\n", region_name); p->mode = NDCTL_NS_MODE_RAW; } /* * NB: We only fail validation if a pfn-specific option is used */ } else if (p->mode == NDCTL_NS_MODE_DEVDAX) { dax = ndctl_region_get_dax_seed(region); if (!dax) { error("Kernel does not support %s mode\n", util_nsmode_name(p->mode)); return -ENODEV; } } if (param.align) { int i, alignments; switch (p->mode) { case NDCTL_NS_MODE_FSDAX: if (!pfn) { error("Kernel does not support setting an alignment in fsdax mode\n"); return -EINVAL; } alignments = ndctl_pfn_get_num_alignments(pfn); break; case NDCTL_NS_MODE_DEVDAX: alignments = ndctl_dax_get_num_alignments(dax); break; default: error("%s mode does not support setting an alignment\n", util_nsmode_name(p->mode)); return -ENXIO; } p->align = parse_size64(param.align); for (i = 0; i < alignments; i++) { uint64_t a; if (p->mode == NDCTL_NS_MODE_FSDAX) a = ndctl_pfn_get_supported_alignment(pfn, i); else a = ndctl_dax_get_supported_alignment(dax, i); if (p->align == a) break; } if (i >= alignments) { error("unsupported align: %s\n", param.align); return -ENXIO; } } else { /* * If we are trying to reconfigure with the same namespace mode, * use the align details from the original namespace. Otherwise * pick the align details from seed namespace */ if (ndns && p->mode == ndctl_namespace_get_mode(ndns)) { struct ndctl_pfn *ns_pfn = ndctl_namespace_get_pfn(ndns); struct ndctl_dax *ns_dax = ndctl_namespace_get_dax(ndns); if (ns_pfn) p->align = ndctl_pfn_get_align(ns_pfn); else if (ns_dax) p->align = ndctl_dax_get_align(ns_dax); else p->align = sysconf(_SC_PAGE_SIZE); } else /* * Use the seed namespace alignment as the default if we need * one. If we don't then use PAGE_SIZE so the size_align * checking works. */ if (p->mode == NDCTL_NS_MODE_FSDAX) { /* * The initial pfn device support in the kernel didn't * have the 'align' sysfs attribute and assumed a 2MB * alignment. Fall back to that if we don't have the * attribute. */ if (pfn && ndctl_pfn_has_align(pfn)) p->align = ndctl_pfn_get_align(pfn); else p->align = SZ_2M; } else if (p->mode == NDCTL_NS_MODE_DEVDAX) { /* * device dax mode was added after the align attribute * so checking for it is unnecessary. */ p->align = ndctl_dax_get_align(dax); } else { p->align = sysconf(_SC_PAGE_SIZE); } /* * Fallback to a page alignment if the region is not aligned * to the default. This is mainly useful for the nfit_test * use case where it is backed by vmalloc memory. */ resource = ndctl_region_get_resource(region); if (resource < ULLONG_MAX && (resource & (p->align - 1))) { debug("%s: falling back to a page alignment\n", region_name); p->align = sysconf(_SC_PAGE_SIZE); } } region_align = ndctl_region_get_align(region); if (region_align < ULONG_MAX && p->size % region_align) { err("%s: align setting is %#lx size %#llx is misaligned\n", region_name, region_align, p->size); return -EINVAL; } size_align = p->align; /* (re-)validate that the size satisfies the alignment */ ways = ndctl_region_get_interleave_ways(region); if (p->size % (size_align * ways)) { char *suffix = ""; if (units == SZ_1K) suffix = "K"; else if (units == SZ_1M) suffix = "M"; else if (units == SZ_1G) suffix = "G"; else if (units == SZ_1T) suffix = "T"; /* * Make the recommendation in the units of the '--size' * option */ size_align = max(units, size_align) * ways; p->size /= size_align; p->size++; if (p->size > ULLONG_MAX / size_align) { err("size overflow: %llu * %llu exceeds ULLONG_MAX\n", p->size, size_align); return -EINVAL; } p->size *= size_align; p->size /= units; err("'--size=' must align to interleave-width: %d and alignment: %ld\n" "did you intend --size=%lld%s?\n", ways, p->align, p->size, suffix); return -EINVAL; } /* * Catch attempts to create sub-16M namespaces to match the * kernel's restriction (see nd_namespace_store()) */ if (p->size < SZ_16M && p->mode != NDCTL_NS_MODE_RAW) { if (default_size) { debug("%s: insufficient capacity for mode: %s\n", region_name, util_nsmode_name(p->mode)); return -EAGAIN; } error("'--size=' must be >= 16MiB for '%s' mode\n", util_nsmode_name(p->mode)); return -EINVAL; } if (param.sector_size) { struct ndctl_btt *btt; int num, i; p->sector_size = parse_size64(param.sector_size); btt = ndctl_region_get_btt_seed(region); if (p->mode == NDCTL_NS_MODE_SECTOR) { if (!btt) { err("%s: does not support 'sector' mode\n", region_name); return -EINVAL; } num = ndctl_btt_get_num_sector_sizes(btt); for (i = 0; i < num; i++) if (ndctl_btt_get_supported_sector_size(btt, i) == p->sector_size) break; if (i >= num) { err("%s: does not support btt sector_size %lu\n", region_name, p->sector_size); return -EINVAL; } } else { struct ndctl_namespace *seed = ndns; if (!seed) { seed = ndctl_region_get_namespace_seed(region); if (!seed) { err("%s: failed to get seed\n", region_name); return -ENXIO; } } num = ndctl_namespace_get_num_sector_sizes(seed); for (i = 0; i < num; i++) if (ndctl_namespace_get_supported_sector_size(seed, i) == p->sector_size) break; if (i >= num) { err("%s: does not support namespace sector_size %lu\n", region_name, p->sector_size); return -EINVAL; } } } else if (ndns) { struct ndctl_btt *btt = ndctl_namespace_get_btt(ndns); /* * If the target mode is still 'safe' carry forward the * sector size, otherwise fall back to what the * namespace supports. */ if (btt && p->mode == NDCTL_NS_MODE_SECTOR) p->sector_size = ndctl_btt_get_sector_size(btt); else p->sector_size = ndctl_namespace_get_sector_size(ndns); } else { struct ndctl_namespace *seed; seed = ndctl_region_get_namespace_seed(region); if (!seed) { err("%s: failed to get seed\n", region_name); return -ENXIO; } if (ndctl_namespace_get_type(seed) == ND_DEVICE_NAMESPACE_BLK) debug("%s: set_defaults() should preclude this?\n", region_name); /* * Pick a default sector size for a pmem namespace based * on what the kernel supports. */ if (ndctl_namespace_get_num_sector_sizes(seed) == 0) p->sector_size = UINT_MAX; else p->sector_size = 512; } if (param.map) { if (!strcmp(param.map, "mem")) p->loc = NDCTL_PFN_LOC_RAM; else p->loc = NDCTL_PFN_LOC_PMEM; if (ndns && p->mode != NDCTL_NS_MODE_FSDAX && p->mode != NDCTL_NS_MODE_DEVDAX) { err("%s: --map= only valid for fsdax mode namespace\n", ndctl_namespace_get_devname(ndns)); return -EINVAL; } } else if (p->mode == NDCTL_NS_MODE_FSDAX || p->mode == NDCTL_NS_MODE_DEVDAX) p->loc = NDCTL_PFN_LOC_PMEM; if (!pfn && do_setup_pfn(ndns, p)) { error("operation failed, %s cannot support requested mode\n", region_name); return -EINVAL; } p->autolabel = param.autolabel; p->autorecover = param.autorecover; return 0; } static struct ndctl_namespace *region_get_namespace(struct ndctl_region *region) { struct ndctl_namespace *ndns; /* prefer the 0th namespace if it is idle */ ndctl_namespace_foreach(region, ndns) if (ndctl_namespace_get_id(ndns) == 0 && ndctl_namespace_is_configuration_idle(ndns)) return ndns; return ndctl_region_get_namespace_seed(region); } static int namespace_create(struct ndctl_region *region) { const char *devname = ndctl_region_get_devname(region); struct ndctl_namespace *ndns; struct parsed_parameters p; int rc; rc = validate_namespace_options(region, NULL, &p); if (rc) return rc; if (ndctl_region_get_ro(region)) { debug("%s: read-only, ineligible for namespace creation\n", devname); return -EAGAIN; } ndns = region_get_namespace(region); if (!ndns || !ndctl_namespace_is_configuration_idle(ndns)) { debug("%s: no %s namespace seed\n", devname, ndns ? "idle" : "available"); return -EAGAIN; } rc = setup_namespace(region, ndns, &p); if (rc && p.autorecover) { ndctl_namespace_set_enforce_mode(ndns, NDCTL_NS_MODE_RAW); ndctl_namespace_delete(ndns); } return rc; } /* * Return convention: * rc < 0 : Error while zeroing, propagate forward * rc == 0 : Successfully cleared the info block, report as destroyed * rc > 0 : skipped, do not count */ static int zero_info_block(struct ndctl_namespace *ndns) { const char *devname = ndctl_namespace_get_devname(ndns); int fd, rc = -ENXIO, info_size = 8192; void *buf = NULL, *read_buf = NULL; char path[50]; ndctl_namespace_set_raw_mode(ndns, 1); rc = ndctl_namespace_enable(ndns); if (rc < 0) { err("%s failed to enable for zeroing, continuing\n", devname); rc = 1; goto out; } if (posix_memalign(&buf, 4096, info_size) != 0) { rc = -ENOMEM; goto out; } if (posix_memalign(&read_buf, 4096, info_size) != 0) { rc = -ENOMEM; goto out; } sprintf(path, "/dev/%s", ndctl_namespace_get_block_device(ndns)); fd = open(path, O_RDWR|O_DIRECT|O_EXCL); if (fd < 0) { err("%s: failed to open %s to zero info block\n", devname, path); goto out; } memset(buf, 0, info_size); rc = pread(fd, read_buf, info_size, 0); if (rc < info_size) { err("%s: failed to read info block, continuing\n", devname); } if (memcmp(buf, read_buf, info_size) == 0) { rc = 1; goto out_close; } rc = pwrite(fd, buf, info_size, 0); if (rc < info_size) { err("%s: failed to zero info block %s\n", devname, path); rc = -ENXIO; } else rc = 0; out_close: close(fd); out: ndctl_namespace_set_raw_mode(ndns, 0); ndctl_namespace_disable_invalidate(ndns); free(read_buf); free(buf); return rc; } static int namespace_prep_reconfig(struct ndctl_region *region, struct ndctl_namespace *ndns) { const char *devname = ndctl_namespace_get_devname(ndns); bool did_zero = false; int rc; if (ndctl_region_get_ro(region)) { error("%s: read-only, re-configuration disabled\n", devname); return -ENXIO; } if (ndctl_namespace_is_active(ndns) && !force) { error("%s is active, specify --force for re-configuration\n", devname); return -EBUSY; } rc = ndctl_namespace_disable_safe(ndns); if (rc) return rc; ndctl_namespace_set_enforce_mode(ndns, NDCTL_NS_MODE_RAW); rc = zero_info_block(ndns); if (rc < 0) return rc; if (rc == 0) did_zero = true; switch (ndctl_namespace_get_type(ndns)) { case ND_DEVICE_NAMESPACE_PMEM: case ND_DEVICE_NAMESPACE_BLK: rc = 2; break; default: /* * for legacy namespaces, we we did any info block * zeroing, we need "processed" to be incremented * but otherwise we are skipping in the count */ if (did_zero) rc = 0; else rc = 1; break; } return rc; } static int namespace_destroy(struct ndctl_region *region, struct ndctl_namespace *ndns) { const char *devname = ndctl_namespace_get_devname(ndns); int rc; rc = namespace_prep_reconfig(region, ndns); if (rc < 0) return rc; /* Labeled namespace, destroy label / allocation */ if (rc == 2) { rc = ndctl_namespace_delete(ndns); if (rc) debug("%s: failed to reclaim\n", devname); } return rc; } static int enable_labels(struct ndctl_region *region) { int mappings = ndctl_region_get_mappings(region); struct ndctl_cmd *cmd_read = NULL; enum ndctl_namespace_version v; struct ndctl_dimm *dimm; int count; /* no dimms => no labels */ if (!mappings) return -ENODEV; count = 0; ndctl_dimm_foreach_in_region(region, dimm) { if (!ndctl_dimm_is_cmd_supported(dimm, ND_CMD_GET_CONFIG_SIZE)) break; if (!ndctl_dimm_is_cmd_supported(dimm, ND_CMD_GET_CONFIG_DATA)) break; if (!ndctl_dimm_is_cmd_supported(dimm, ND_CMD_SET_CONFIG_DATA)) break; count++; } /* all the dimms must support labeling */ if (count != mappings) return -ENODEV; ndctl_region_disable_invalidate(region); count = 0; ndctl_dimm_foreach_in_region(region, dimm) if (ndctl_dimm_is_active(dimm)) { warning("%s is active in %s, failing autolabel\n", ndctl_dimm_get_devname(dimm), ndctl_region_get_devname(region)); count++; } /* some of the dimms belong to multiple regions?? */ if (count) goto out; v = NDCTL_NS_VERSION_1_2; retry: ndctl_dimm_foreach_in_region(region, dimm) { int num_labels, avail; ndctl_cmd_unref(cmd_read); cmd_read = ndctl_dimm_read_label_index(dimm); if (!cmd_read) continue; num_labels = ndctl_dimm_init_labels(dimm, v); if (num_labels < 0) continue; ndctl_dimm_disable(dimm); ndctl_dimm_enable(dimm); /* * If the kernel appears to not understand v1.2 labels, * try v1.1. Note, we increment avail by 1 to account * for the one free label that the kernel always * maintains for ongoing updates. */ avail = ndctl_dimm_get_available_labels(dimm) + 1; if (num_labels != avail && v == NDCTL_NS_VERSION_1_2) { v = NDCTL_NS_VERSION_1_1; goto retry; } } ndctl_cmd_unref(cmd_read); out: ndctl_region_enable(region); if (ndctl_region_get_nstype(region) != ND_DEVICE_NAMESPACE_PMEM) { err("%s: failed to initialize labels\n", ndctl_region_get_devname(region)); return -EBUSY; } return 0; } static int namespace_reconfig(struct ndctl_region *region, struct ndctl_namespace *ndns) { struct parsed_parameters p; int rc; rc = validate_namespace_options(region, ndns, &p); if (rc) return rc; rc = namespace_prep_reconfig(region, ndns); if (rc < 0) return rc; /* check if we can enable labels on this region */ if (ndctl_region_get_nstype(region) == ND_DEVICE_NAMESPACE_IO && p.autolabel) { /* * If this fails, try to continue label-less, if this * got far enough to invalidate the region than @ndns is * now invalid. */ rc = enable_labels(region); if (rc != -ENODEV) ndns = region_get_namespace(region); if (!ndns || (rc != -ENODEV && !ndctl_namespace_is_configuration_idle(ndns))) { debug("%s: no %s namespace seed\n", ndctl_region_get_devname(region), ndns ? "idle" : "available"); return -ENODEV; } } return setup_namespace(region, ndns, &p); } int namespace_check(struct ndctl_namespace *ndns, bool verbose, bool force, bool repair, bool logfix); static int bus_send_clear(struct ndctl_bus *bus, unsigned long long start, unsigned long long size) { const char *busname = ndctl_bus_get_provider(bus); struct ndctl_cmd *cmd_cap, *cmd_clear; unsigned long long cleared; struct ndctl_range range; int rc; /* get ars_cap */ cmd_cap = ndctl_bus_cmd_new_ars_cap(bus, start, size); if (!cmd_cap) { err("bus: %s failed to create cmd\n", busname); return -ENOTTY; } rc = ndctl_cmd_submit_xlat(cmd_cap); if (rc < 0) { err("bus: %s failed to submit cmd: %d\n", busname, rc); goto out_cap; } /* send clear_error */ if (ndctl_cmd_ars_cap_get_range(cmd_cap, &range)) { err("bus: %s failed to get ars_cap range\n", busname); rc = -ENXIO; goto out_cap; } cmd_clear = ndctl_bus_cmd_new_clear_error(range.address, range.length, cmd_cap); if (!cmd_clear) { err("bus: %s failed to create cmd\n", busname); rc = -ENOTTY; goto out_cap; } rc = ndctl_cmd_submit_xlat(cmd_clear); if (rc < 0) { err("bus: %s failed to submit cmd: %d\n", busname, rc); goto out_clr; } cleared = ndctl_cmd_clear_error_get_cleared(cmd_clear); if (cleared != range.length) { err("bus: %s expected to clear: %lld actual: %lld\n", busname, range.length, cleared); rc = -ENXIO; } out_clr: ndctl_cmd_unref(cmd_clear); out_cap: ndctl_cmd_unref(cmd_cap); return rc; } static int nstype_clear_badblocks(struct ndctl_namespace *ndns, const char *devname, unsigned long long dev_begin, unsigned long long dev_size) { struct ndctl_region *region = ndctl_namespace_get_region(ndns); struct ndctl_bus *bus = ndctl_region_get_bus(region); unsigned long long region_begin, dev_end; unsigned int cleared = 0; struct badblock *bb; int rc = 0; region_begin = ndctl_region_get_resource(region); if (region_begin == ULLONG_MAX) { rc = -errno; if (ndctl_namespace_enable(ndns) < 0) error("%s: failed to reenable namespace\n", devname); return rc; } dev_end = dev_begin + dev_size - 1; ndctl_region_badblock_foreach(region, bb) { unsigned long long bb_begin, bb_end, bb_len; bb_begin = region_begin + (bb->offset << 9); bb_len = (unsigned long long)bb->len << 9; bb_end = bb_begin + bb_len - 1; /* bb is not fully contained in the usable area */ if (bb_begin < dev_begin || bb_end > dev_end) continue; rc = bus_send_clear(bus, bb_begin, bb_len); if (rc) { error("%s: failed to clear badblock at {%lld, %u}\n", devname, bb->offset, bb->len); break; } cleared += bb->len; } debug("%s: cleared %u badblocks\n", devname, cleared); rc = ndctl_namespace_enable(ndns); if (rc < 0) return rc; return 0; } static int dax_clear_badblocks(struct ndctl_dax *dax) { struct ndctl_namespace *ndns = ndctl_dax_get_namespace(dax); const char *devname = ndctl_dax_get_devname(dax); unsigned long long begin, size; int rc; begin = ndctl_dax_get_resource(dax); if (begin == ULLONG_MAX) return -ENXIO; size = ndctl_dax_get_size(dax); if (size == ULLONG_MAX) return -ENXIO; rc = ndctl_namespace_disable_safe(ndns); if (rc) { error("%s: unable to disable namespace: %s\n", devname, strerror(-rc)); return rc; } return nstype_clear_badblocks(ndns, devname, begin, size); } static int pfn_clear_badblocks(struct ndctl_pfn *pfn) { struct ndctl_namespace *ndns = ndctl_pfn_get_namespace(pfn); const char *devname = ndctl_pfn_get_devname(pfn); unsigned long long begin, size; int rc; begin = ndctl_pfn_get_resource(pfn); if (begin == ULLONG_MAX) return -ENXIO; size = ndctl_pfn_get_size(pfn); if (size == ULLONG_MAX) return -ENXIO; rc = ndctl_namespace_disable_safe(ndns); if (rc) { error("%s: unable to disable namespace: %s\n", devname, strerror(-rc)); return rc; } return nstype_clear_badblocks(ndns, devname, begin, size); } static int raw_clear_badblocks(struct ndctl_namespace *ndns) { const char *devname = ndctl_namespace_get_devname(ndns); unsigned long long begin, size; int rc; begin = ndctl_namespace_get_resource(ndns); if (begin == ULLONG_MAX) return -ENXIO; size = ndctl_namespace_get_size(ndns); if (size == ULLONG_MAX) return -ENXIO; rc = ndctl_namespace_disable_safe(ndns); if (rc) { error("%s: unable to disable namespace: %s\n", devname, strerror(-rc)); return rc; } return nstype_clear_badblocks(ndns, devname, begin, size); } static int namespace_wait_scrub(struct ndctl_namespace *ndns, bool do_scrub) { const char *devname = ndctl_namespace_get_devname(ndns); struct ndctl_bus *bus = ndctl_namespace_get_bus(ndns); int in_progress, rc; in_progress = ndctl_bus_get_scrub_state(bus); if (in_progress < 0) { error("%s: Unable to determine scrub state: %s\n", devname, strerror(-in_progress)); return in_progress; } /* start a scrub if asked and if one isn't in progress */ if (do_scrub && (!in_progress)) { rc = ndctl_bus_start_scrub(bus); if (rc) { error("%s: Unable to start scrub: %s\n", devname, strerror(-rc)); return rc; } } /* * wait for any in-progress scrub, whether started above, or * started automatically at boot time */ rc = ndctl_bus_wait_for_scrub_completion(bus); if (rc) { error("%s: Error waiting for scrub: %s\n", devname, strerror(-rc)); return rc; } return 0; } static int namespace_clear_bb(struct ndctl_namespace *ndns, bool do_scrub) { struct ndctl_pfn *pfn = ndctl_namespace_get_pfn(ndns); struct ndctl_dax *dax = ndctl_namespace_get_dax(ndns); struct ndctl_btt *btt = ndctl_namespace_get_btt(ndns); struct json_object *jndns; int rc; if (btt) { /* skip btt error clearing for now */ debug("%s: skip error clearing for btt\n", ndctl_btt_get_devname(btt)); return 1; } rc = namespace_wait_scrub(ndns, do_scrub); if (rc) return rc; if (dax) rc = dax_clear_badblocks(dax); else if (pfn) rc = pfn_clear_badblocks(pfn); else rc = raw_clear_badblocks(ndns); if (rc) return rc; jndns = util_namespace_to_json(ndns, UTIL_JSON_MEDIA_ERRORS); if (jndns) printf("%s\n", json_object_to_json_string_ext(jndns, JSON_C_TO_STRING_PRETTY)); return 0; } struct read_infoblock_ctx { struct json_object *jblocks; FILE *f_out; }; #define parse_field(sb, field) \ do { \ jobj = json_object_new_int(le32_to_cpu((sb)->field)); \ if (!jobj) \ goto err; \ json_object_object_add(jblock, #field, jobj); \ } while (0) #define parse_hex(sb, field, sz) \ do { \ jobj = util_json_object_hex(le##sz##_to_cpu((sb)->field), flags); \ if (!jobj) \ goto err; \ json_object_object_add(jblock, #field, jobj); \ } while (0) static json_object *btt_parse(struct btt_sb *btt_sb, struct ndctl_namespace *ndns, const char *path, unsigned long flags) { uuid_t uuid; char str[40]; struct json_object *jblock, *jobj; const char *cmd = "read-infoblock"; const bool verify = param.verify; if (verify && !verify_infoblock_checksum((union info_block *) btt_sb)) { pr_verbose("%s: %s checksum verification failed\n", cmd, __func__); return NULL; } if (ndns) { ndctl_namespace_get_uuid(ndns, uuid); if (verify && !uuid_is_null(uuid) && memcmp(uuid, btt_sb->parent_uuid, sizeof(uuid) != 0)) { pr_verbose("%s: %s uuid verification failed\n", cmd, __func__); return NULL; } } jblock = json_object_new_object(); if (!jblock) return NULL; if (ndns) { jobj = json_object_new_string(ndctl_namespace_get_devname(ndns)); if (!jobj) goto err; json_object_object_add(jblock, "dev", jobj); } else { jobj = json_object_new_string(path); if (!jobj) goto err; json_object_object_add(jblock, "file", jobj); } jobj = json_object_new_string((char *) btt_sb->signature); if (!jobj) goto err; json_object_object_add(jblock, "signature", jobj); uuid_unparse((void *) btt_sb->uuid, str); jobj = json_object_new_string(str); if (!jobj) goto err; json_object_object_add(jblock, "uuid", jobj); uuid_unparse((void *) btt_sb->parent_uuid, str); jobj = json_object_new_string(str); if (!jobj) goto err; json_object_object_add(jblock, "parent_uuid", jobj); jobj = util_json_object_hex(le32_to_cpu(btt_sb->flags), flags); if (!jobj) goto err; json_object_object_add(jblock, "flags", jobj); if (snprintf(str, 4, "%d.%d", btt_sb->version_major, btt_sb->version_minor) >= 4) goto err; jobj = json_object_new_string(str); if (!jobj) goto err; json_object_object_add(jblock, "version", jobj); parse_field(btt_sb, external_lbasize); parse_field(btt_sb, external_nlba); parse_field(btt_sb, internal_lbasize); parse_field(btt_sb, internal_nlba); parse_field(btt_sb, nfree); parse_field(btt_sb, infosize); parse_hex(btt_sb, nextoff, 64); parse_hex(btt_sb, dataoff, 64); parse_hex(btt_sb, mapoff, 64); parse_hex(btt_sb, logoff, 64); parse_hex(btt_sb, info2off, 64); return jblock; err: pr_verbose("%s: failed to create json representation\n", cmd); json_object_put(jblock); return NULL; } static json_object *pfn_parse(struct pfn_sb *pfn_sb, struct ndctl_namespace *ndns, const char *path, unsigned long flags) { uuid_t uuid; char str[40]; struct json_object *jblock, *jobj; const char *cmd = "read-infoblock"; const bool verify = param.verify; if (verify && !verify_infoblock_checksum((union info_block *) pfn_sb)) { pr_verbose("%s: %s checksum verification failed\n", cmd, __func__); return NULL; } if (ndns) { ndctl_namespace_get_uuid(ndns, uuid); if (verify && !uuid_is_null(uuid) && memcmp(uuid, pfn_sb->parent_uuid, sizeof(uuid) != 0)) { pr_verbose("%s: %s uuid verification failed\n", cmd, __func__); return NULL; } } jblock = json_object_new_object(); if (!jblock) return NULL; if (ndns) { jobj = json_object_new_string(ndctl_namespace_get_devname(ndns)); if (!jobj) goto err; json_object_object_add(jblock, "dev", jobj); } else { jobj = json_object_new_string(path); if (!jobj) goto err; json_object_object_add(jblock, "file", jobj); } jobj = json_object_new_string((char *) pfn_sb->signature); if (!jobj) goto err; json_object_object_add(jblock, "signature", jobj); uuid_unparse((void *) pfn_sb->uuid, str); jobj = json_object_new_string(str); if (!jobj) goto err; json_object_object_add(jblock, "uuid", jobj); uuid_unparse((void *) pfn_sb->parent_uuid, str); jobj = json_object_new_string(str); if (!jobj) goto err; json_object_object_add(jblock, "parent_uuid", jobj); jobj = util_json_object_hex(le32_to_cpu(pfn_sb->flags), flags); if (!jobj) goto err; json_object_object_add(jblock, "flags", jobj); if (snprintf(str, 4, "%d.%d", pfn_sb->version_major, pfn_sb->version_minor) >= 4) goto err; jobj = json_object_new_string(str); if (!jobj) goto err; json_object_object_add(jblock, "version", jobj); parse_hex(pfn_sb, dataoff, 64); parse_hex(pfn_sb, npfns, 64); parse_field(pfn_sb, mode); parse_hex(pfn_sb, start_pad, 32); parse_hex(pfn_sb, end_trunc, 32); parse_hex(pfn_sb, align, 32); parse_hex(pfn_sb, page_size, 32); parse_hex(pfn_sb, page_struct_size, 16); return jblock; err: pr_verbose("%s: failed to create json representation\n", cmd); json_object_put(jblock); return NULL; } #define INFOBLOCK_SZ SZ_8K static int parse_namespace_infoblock(char *_buf, struct ndctl_namespace *ndns, const char *path, struct read_infoblock_ctx *ri_ctx) { int rc; void *buf = _buf; unsigned long flags = param.human ? UTIL_JSON_HUMAN : 0; struct btt_sb *btt1_sb = buf + SZ_4K, *btt2_sb = buf; struct json_object *jblock = NULL, *jblocks = ri_ctx->jblocks; struct pfn_sb *pfn_sb = buf + SZ_4K, *dax_sb = buf + SZ_4K; if (!param.json && !param.human) { rc = fwrite(buf, 1, INFOBLOCK_SZ, ri_ctx->f_out); if (rc != INFOBLOCK_SZ) return -EIO; fflush(ri_ctx->f_out); return 0; } if (!jblocks) { jblocks = json_object_new_array(); if (!jblocks) return -ENOMEM; ri_ctx->jblocks = jblocks; } if (memcmp(btt1_sb->signature, BTT_SIG, BTT_SIG_LEN) == 0) { jblock = btt_parse(btt1_sb, ndns, path, flags); if (jblock) json_object_array_add(jblocks, jblock); } if (memcmp(btt2_sb->signature, BTT_SIG, BTT_SIG_LEN) == 0) { jblock = btt_parse(btt2_sb, ndns, path, flags); if (jblock) json_object_array_add(jblocks, jblock); } if (memcmp(pfn_sb->signature, PFN_SIG, PFN_SIG_LEN) == 0) { jblock = pfn_parse(pfn_sb, ndns, path, flags); if (jblock) json_object_array_add(jblocks, jblock); } if (memcmp(dax_sb->signature, DAX_SIG, PFN_SIG_LEN) == 0) { jblock = pfn_parse(dax_sb, ndns, path, flags); if (jblock) json_object_array_add(jblocks, jblock); } return 0; } static int file_read_infoblock(const char *path, struct ndctl_namespace *ndns, struct read_infoblock_ctx *ri_ctx) { const char *devname = ndns ? ndctl_namespace_get_devname(ndns) : ""; const char *cmd = "read-infoblock"; void *buf = NULL; int fd = -1, rc; buf = calloc(1, INFOBLOCK_SZ); if (!buf) return -ENOMEM; if (!path) { fd = STDIN_FILENO; path = ""; } else fd = open(path, O_RDONLY|O_EXCL); if (fd < 0) { pr_verbose("%s: %s failed to open %s: %s\n", cmd, devname, path, strerror(errno)); rc = -errno; goto out; } rc = read(fd, buf, INFOBLOCK_SZ); if (rc < INFOBLOCK_SZ) { pr_verbose("%s: %s failed to read %s: %s\n", cmd, devname, path, strerror(errno)); if (rc < 0) rc = -errno; else rc = -EIO; goto out; } rc = parse_namespace_infoblock(buf, ndns, path, ri_ctx); out: free(buf); if (fd >= 0 && fd != STDIN_FILENO) close(fd); return rc; } static unsigned long PHYS_PFN(unsigned long long phys) { return phys / sysconf(_SC_PAGE_SIZE); } #define SUBSECTION_SHIFT 21 #define SUBSECTION_SIZE (1UL << SUBSECTION_SHIFT) #define MAX_STRUCT_PAGE_SIZE 64 /* Derived from nd_pfn_init() in kernel version v5.5 */ static int write_pfn_sb(int fd, unsigned long long size, const char *sig, void *buf) { unsigned long npfns, align, pfn_align; struct pfn_sb *pfn_sb = buf + SZ_4K; unsigned long long start, offset; uuid_t uuid, parent_uuid; u32 end_trunc, start_pad; enum pfn_mode mode; u64 checksum; int rc; start = parse_size64(param.offset); if (start == ULLONG_MAX) { err("failed to parse offset option '%s'\n", param.offset); return -EINVAL; } npfns = PHYS_PFN(size - SZ_8K); pfn_align = parse_size64(param.align); align = max(pfn_align, SUBSECTION_SIZE); if (param.uuid) { if (uuid_parse(param.uuid, uuid)) return -EINVAL; } else { uuid_generate(uuid); } if (param.parent_uuid) { if (uuid_parse(param.parent_uuid, parent_uuid)) return -EINVAL; } else { memset(parent_uuid, 0, sizeof(uuid_t)); } if (strcmp(param.map, "dev") == 0) mode = PFN_MODE_PMEM; else mode = PFN_MODE_RAM; /* * Unlike the kernel implementation always set start_pad and * end_trunc relative to the specified --offset= option to allow * testing legacy / "buggy" configurations. */ start_pad = ALIGN(start, align) - start; end_trunc = start + size - ALIGN_DOWN(start + size, align); if (mode == PFN_MODE_PMEM) { /* * The altmap should be padded out to the block size used * when populating the vmemmap. This *should* be equal to * PMD_SIZE for most architectures. * * Also make sure size of struct page is less than 64. We * want to make sure we use large enough size here so that * we don't have a dynamic reserve space depending on * struct page size. But we also want to make sure we notice * when we end up adding new elements to struct page. */ if (start > ULLONG_MAX - (SZ_8K + MAX_STRUCT_PAGE_SIZE * npfns)) { error("integer overflow in offset calculation\n"); return -EINVAL; } offset = ALIGN(start + SZ_8K + MAX_STRUCT_PAGE_SIZE * npfns, align) - start; } else offset = ALIGN(start + SZ_8K, align) - start; if (offset >= size) { error("unable to satisfy requested alignment\n"); return -ENXIO; } npfns = PHYS_PFN(size - offset - end_trunc - start_pad); pfn_sb->mode = cpu_to_le32(mode); pfn_sb->dataoff = cpu_to_le64(offset); pfn_sb->npfns = cpu_to_le64(npfns); memcpy(pfn_sb->signature, sig, PFN_SIG_LEN); memcpy(pfn_sb->uuid, uuid, 16); memcpy(pfn_sb->parent_uuid, parent_uuid, 16); pfn_sb->version_major = cpu_to_le16(1); pfn_sb->version_minor = cpu_to_le16(4); pfn_sb->start_pad = cpu_to_le32(start_pad); pfn_sb->end_trunc = cpu_to_le32(end_trunc); pfn_sb->align = cpu_to_le32(pfn_align); pfn_sb->page_struct_size = cpu_to_le16(MAX_STRUCT_PAGE_SIZE); pfn_sb->page_size = cpu_to_le32(sysconf(_SC_PAGE_SIZE)); checksum = fletcher64(pfn_sb, sizeof(*pfn_sb), 0); pfn_sb->checksum = cpu_to_le64(checksum); rc = write(fd, buf, INFOBLOCK_SZ); if (rc < INFOBLOCK_SZ) return -EIO; return 0; } static int file_write_infoblock(const char *path) { unsigned long long size = parse_size64(param.size); int fd = -1, rc; void *buf; if (param.std_out) fd = STDOUT_FILENO; else { fd = open(path, O_CREAT|O_RDWR, 0644); if (fd < 0) { error("failed to open: %s\n", path); return -errno; } if (!size) { struct stat st; rc = fstat(fd, &st); if (rc < 0) { error("failed to stat: %s\n", path); rc = -errno; goto out; } if (S_ISREG(st.st_mode)) size = st.st_size; else if (S_ISBLK(st.st_mode)) { rc = ioctl(fd, BLKGETSIZE64, &size); if (rc < 0) { error("failed to retrieve size: %s\n", path); rc = -errno; goto out; } } else { error("unsupported file type: %s\n", path); rc = -EINVAL; goto out; } } } buf = calloc(INFOBLOCK_SZ, 1); if (!buf) { rc = -ENOMEM; goto out; } switch (util_nsmode(param.mode)) { case NDCTL_NS_MODE_FSDAX: rc = write_pfn_sb(fd, size, PFN_SIG, buf); break; case NDCTL_NS_MODE_DEVDAX: rc = write_pfn_sb(fd, size, DAX_SIG, buf); break; default: rc = -EINVAL; break; } free(buf); out: if (fd >= 0 && fd != STDOUT_FILENO) close(fd); return rc; } static unsigned long ndctl_get_default_alignment(struct ndctl_namespace *ndns) { unsigned long long align = 0; struct ndctl_dax *dax = ndctl_namespace_get_dax(ndns); struct ndctl_pfn *pfn = ndctl_namespace_get_pfn(ndns); if (ndctl_namespace_get_mode(ndns) == NDCTL_NS_MODE_FSDAX && pfn) align = ndctl_pfn_get_supported_alignment(pfn, 1); else if (ndctl_namespace_get_mode(ndns) == NDCTL_NS_MODE_DEVDAX && dax) align = ndctl_dax_get_supported_alignment(dax, 1); if (!align) align = sysconf(_SC_PAGE_SIZE); return align; } static int namespace_rw_infoblock(struct ndctl_namespace *ndns, struct read_infoblock_ctx *ri_ctx, int write) { int rc; uuid_t uuid; char str[40]; char path[50]; const char *save; const char *cmd = write ? "write-infoblock" : "read-infoblock"; const char *devname = ndctl_namespace_get_devname(ndns); if (ndctl_namespace_is_active(ndns)) { pr_verbose("%s: %s enabled, must be disabled\n", cmd, devname); return -EBUSY; } ndctl_namespace_set_raw_mode(ndns, 1); rc = ndctl_namespace_enable(ndns); if (rc < 0) { pr_verbose("%s: %s failed to enable\n", cmd, devname); goto out; } save = param.parent_uuid; if (!param.parent_uuid) { ndctl_namespace_get_uuid(ndns, uuid); uuid_unparse(uuid, str); param.parent_uuid = str; } sprintf(path, "/dev/%s", ndctl_namespace_get_block_device(ndns)); if (write) { unsigned long long align; bool align_provided = true; if (!param.align) { align = ndctl_get_default_alignment(ndns); if (asprintf((char **)¶m.align, "%llu", align) < 0) { rc = -EINVAL; goto out; } align_provided = false; } if (param.size) { unsigned long long size = parse_size64(param.size); align = parse_size64(param.align); if (align == 0 || align == ULLONG_MAX) { error("invalid alignment:%s\n", param.align); rc = -EINVAL; } if (!IS_ALIGNED(size, align)) { error("--size=%s not aligned to %s\n", param.size, param.align); rc = -EINVAL; } } if (!rc) rc = file_write_infoblock(path); if (!align_provided) { free((char *)param.align); param.align = NULL; } } else rc = file_read_infoblock(path, ndns, ri_ctx); param.parent_uuid = save; out: ndctl_namespace_set_raw_mode(ndns, 0); ndctl_namespace_disable_invalidate(ndns); return rc; } static int do_xaction_namespace(const char *namespace, enum device_action action, struct ndctl_ctx *ctx, int *processed) { struct read_infoblock_ctx ri_ctx = { 0 }; struct ndctl_namespace *ndns, *_n; int rc = -ENXIO, saved_rc = 0; struct ndctl_region *region; const char *ndns_name; struct ndctl_bus *bus; *processed = 0; if (action == ACTION_READ_INFOBLOCK) { if (!param.outfile) ri_ctx.f_out = stdout; else { ri_ctx.f_out = fopen(param.outfile, "w+"); if (!ri_ctx.f_out) { fprintf(stderr, "failed to open: %s: (%s)\n", param.outfile, strerror(errno)); return -errno; } } if (param.infile || !namespace) { rc = file_read_infoblock(param.infile, NULL, &ri_ctx); if (ri_ctx.jblocks) util_display_json_array(ri_ctx.f_out, ri_ctx.jblocks, 0); if (rc >= 0) (*processed)++; goto out_close; } } if (action == ACTION_WRITE_INFOBLOCK && !namespace) { if (!param.align) param.align = "2M"; rc = file_write_infoblock(param.outfile); if (rc >= 0) (*processed)++; goto out_close; } if (!namespace && action != ACTION_CREATE) goto out_close; if (namespace && (strcmp(namespace, "all") == 0)) rc = 0; if (verbose) ndctl_set_log_priority(ctx, LOG_DEBUG); if (action == ACTION_ENABLE) cmd_name = "enable namespace"; else if (action == ACTION_DISABLE) cmd_name = "disable namespace"; else if (action == ACTION_CREATE) cmd_name = "create namespace"; else if (action == ACTION_DESTROY) cmd_name = "destroy namespace"; else if (action == ACTION_CHECK) cmd_name = "check namespace"; else if (action == ACTION_CLEAR) cmd_name = "clear errors namespace"; ndctl_bus_foreach(ctx, bus) { bool do_scrub; if (!util_bus_filter(bus, param.bus)) continue; /* only request scrubbing once per bus */ do_scrub = scrub; ndctl_region_foreach(bus, region) { if (!util_region_filter(region, param.region)) continue; if (param.type) { if (strcmp(param.type, "pmem") == 0 && ndctl_region_get_type(region) == ND_DEVICE_REGION_PMEM) /* pass */; else if (strcmp(param.type, "blk") == 0 && ndctl_region_get_type(region) == ND_DEVICE_REGION_BLK) /* pass */; else continue; } if (action == ACTION_CREATE && !namespace) { rc = namespace_create(region); if (rc == -EAGAIN) continue; if (rc == 0) { (*processed)++; if (param.greedy) continue; } else if (param.greedy && force) { saved_rc = rc; continue; } goto out_close; } ndctl_namespace_foreach_safe(region, ndns, _n) { ndns_name = ndctl_namespace_get_devname(ndns); if (strcmp(namespace, "all") == 0) { static const uuid_t zero_uuid; uuid_t uuid; ndctl_namespace_get_uuid(ndns, uuid); if (!ndctl_namespace_get_size(ndns) && !memcmp(uuid, zero_uuid, sizeof(uuid_t))) continue; } else { if (strcmp(namespace, ndns_name) != 0) continue; } switch (action) { case ACTION_DISABLE: rc = ndctl_namespace_disable_safe(ndns); if (rc == 0) (*processed)++; break; case ACTION_ENABLE: rc = ndctl_namespace_enable(ndns); if (rc >= 0) { (*processed)++; rc = 0; } break; case ACTION_DESTROY: rc = namespace_destroy(region, ndns); if (rc == 0) (*processed)++; /* return success if skipped */ if (rc > 0) rc = 0; break; case ACTION_CHECK: rc = namespace_check(ndns, verbose, force, repair, logfix); if (rc == 0) (*processed)++; break; case ACTION_CLEAR: rc = namespace_clear_bb(ndns, do_scrub); /* one scrub per bus is sufficient */ do_scrub = false; if (rc == 0) (*processed)++; break; case ACTION_CREATE: rc = namespace_reconfig(region, ndns); if (rc == 0) *processed = 1; return rc; case ACTION_READ_INFOBLOCK: rc = namespace_rw_infoblock(ndns, &ri_ctx, READ); if (rc == 0) (*processed)++; break; case ACTION_WRITE_INFOBLOCK: rc = namespace_rw_infoblock(ndns, NULL, WRITE); if (rc == 0) (*processed)++; break; default: rc = -EINVAL; break; } } } } if (ri_ctx.jblocks) util_display_json_array(ri_ctx.f_out, ri_ctx.jblocks, 0); if (action == ACTION_CREATE && rc == -EAGAIN) { /* * Namespace creation searched through all candidate * regions and all of them said "nope, I don't have * enough capacity", so report -ENOSPC. Except during * greedy namespace creation using --continue as we * may have created some namespaces already, and the * last one in the region search may preexist. */ if (param.greedy && (*processed) > 0) rc = 0; else rc = -ENOSPC; } out_close: if (ri_ctx.f_out && ri_ctx.f_out != stdout) fclose(ri_ctx.f_out); if (saved_rc) rc = saved_rc; return rc; } int cmd_disable_namespace(int argc, const char **argv, struct ndctl_ctx *ctx) { char *xable_usage = "ndctl disable-namespace []"; const char *namespace = parse_namespace_options(argc, argv, ACTION_DISABLE, base_options, xable_usage); int disabled, rc; rc = do_xaction_namespace(namespace, ACTION_DISABLE, ctx, &disabled); if (rc < 0 && !err_count) fprintf(stderr, "error disabling namespaces: %s\n", strerror(-rc)); fprintf(stderr, "disabled %d namespace%s\n", disabled, disabled == 1 ? "" : "s"); return rc; } int cmd_enable_namespace(int argc, const char **argv, struct ndctl_ctx *ctx) { char *xable_usage = "ndctl enable-namespace []"; const char *namespace = parse_namespace_options(argc, argv, ACTION_ENABLE, base_options, xable_usage); int enabled, rc; rc = do_xaction_namespace(namespace, ACTION_ENABLE, ctx, &enabled); if (rc < 0 && !err_count) fprintf(stderr, "error enabling namespaces: %s\n", strerror(-rc)); fprintf(stderr, "enabled %d namespace%s\n", enabled, enabled == 1 ? "" : "s"); return rc; } int cmd_create_namespace(int argc, const char **argv, struct ndctl_ctx *ctx) { char *xable_usage = "ndctl create-namespace []"; const char *namespace = parse_namespace_options(argc, argv, ACTION_CREATE, create_options, xable_usage); int created, rc; rc = do_xaction_namespace(namespace, ACTION_CREATE, ctx, &created); if (rc < 0 && created < 1 && param.do_scan) { /* * In the default scan case we try pmem first and then * fallback to blk before giving up. */ memset(¶m, 0, sizeof(param)); param.type = "blk"; set_defaults(ACTION_CREATE); rc = do_xaction_namespace(NULL, ACTION_CREATE, ctx, &created); } if (param.greedy) fprintf(stderr, "created %d namespace%s\n", created, created == 1 ? "" : "s"); if ((rc < 0 || (!namespace && created < 1)) && !err_count) { fprintf(stderr, "failed to %s namespace: %s\n", namespace ? "reconfigure" : "create", strerror(-rc)); if (!namespace) rc = -ENODEV; } return rc; } int cmd_destroy_namespace(int argc , const char **argv, struct ndctl_ctx *ctx) { char *xable_usage = "ndctl destroy-namespace []"; const char *namespace = parse_namespace_options(argc, argv, ACTION_DESTROY, destroy_options, xable_usage); int destroyed, rc; rc = do_xaction_namespace(namespace, ACTION_DESTROY, ctx, &destroyed); if (rc < 0 && !err_count) fprintf(stderr, "error destroying namespaces: %s\n", strerror(-rc)); fprintf(stderr, "destroyed %d namespace%s\n", destroyed, destroyed == 1 ? "" : "s"); return rc; } int cmd_check_namespace(int argc , const char **argv, struct ndctl_ctx *ctx) { char *xable_usage = "ndctl check-namespace []"; const char *namespace = parse_namespace_options(argc, argv, ACTION_CHECK, check_options, xable_usage); int checked, rc; rc = do_xaction_namespace(namespace, ACTION_CHECK, ctx, &checked); if (rc < 0 && !err_count) fprintf(stderr, "error checking namespaces: %s\n", strerror(-rc)); fprintf(stderr, "checked %d namespace%s\n", checked, checked == 1 ? "" : "s"); return rc; } int cmd_clear_errors(int argc , const char **argv, struct ndctl_ctx *ctx) { char *xable_usage = "ndctl clear_errors []"; const char *namespace = parse_namespace_options(argc, argv, ACTION_CLEAR, clear_options, xable_usage); int cleared, rc; rc = do_xaction_namespace(namespace, ACTION_CLEAR, ctx, &cleared); if (rc < 0 && !err_count) fprintf(stderr, "error clearing namespaces: %s\n", strerror(-rc)); fprintf(stderr, "cleared %d namespace%s\n", cleared, cleared == 1 ? "" : "s"); return rc; } int cmd_read_infoblock(int argc, const char **argv, struct ndctl_ctx *ctx) { char *xable_usage = "ndctl read-infoblock []"; const char *namespace = parse_namespace_options(argc, argv, ACTION_READ_INFOBLOCK, read_infoblock_options, xable_usage); int read, rc; rc = do_xaction_namespace(namespace, ACTION_READ_INFOBLOCK, ctx, &read); if (rc < 0) fprintf(stderr, "error reading infoblock data: %s\n", strerror(-rc)); fprintf(stderr, "read %d infoblock%s\n", read, read == 1 ? "" : "s"); return rc; } int cmd_write_infoblock(int argc, const char **argv, struct ndctl_ctx *ctx) { char *xable_usage = "ndctl write-infoblock []"; const char *namespace = parse_namespace_options(argc, argv, ACTION_WRITE_INFOBLOCK, write_infoblock_options, xable_usage); int write, rc; rc = do_xaction_namespace(namespace, ACTION_WRITE_INFOBLOCK, ctx, &write); if (rc < 0) fprintf(stderr, "error checking infoblocks: %s\n", strerror(-rc)); fprintf(stderr, "wrote %d infoblock%s\n", write, write == 1 ? "" : "s"); return rc; } ndctl-81/ndctl/namespace.h000066400000000000000000000142761476737544500156610ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1 */ /* Copyright (C) 2014-2020, Intel Corporation. All rights reserved. */ #ifndef __NDCTL_NAMESPACE_H__ #define __NDCTL_NAMESPACE_H__ #include #include #include #include #include #include enum { NSINDEX_SIG_LEN = 16, NSINDEX_ALIGN = 256, NSINDEX_SEQ_MASK = 0x3, NSLABEL_UUID_LEN = 16, NSLABEL_NAMESPACE_MIN_SIZE = SZ_16M, NSLABEL_NAME_LEN = 64, }; /** * struct namespace_index - label set superblock * @sig: NAMESPACE_INDEX\0 * @flags: placeholder * @seq: sequence number for this index * @myoff: offset of this index in label area * @mysize: size of this index struct * @otheroff: offset of other index * @labeloff: offset of first label slot * @nslot: total number of label slots * @major: label area major version * @minor: label area minor version * @checksum: fletcher64 of all fields * @free[0]: bitmap, nlabel bits * * The size of free[] is rounded up so the total struct size is a * multiple of NSINDEX_ALIGN bytes. Any bits this allocates beyond * nlabel bits must be zero. */ struct namespace_index { char sig[NSINDEX_SIG_LEN]; u8 flags[3]; u8 labelsize; le32 seq; le64 myoff; le64 mysize; le64 otheroff; le64 labeloff; le32 nslot; le16 major; le16 minor; le64 checksum; char free[0]; }; /** * struct namespace_label - namespace superblock * @uuid: UUID per RFC 4122 * @name: optional name (NULL-terminated) * @flags: see NSLABEL_FLAG_* * @nlabel: num labels to describe this ns * @position: labels position in set * @isetcookie: interleave set cookie * @lbasize: LBA size in bytes or 0 for pmem * @dpa: DPA of NVM range on this DIMM * @rawsize: size of namespace * @slot: slot of this label in label area */ struct namespace_label { char uuid[NSLABEL_UUID_LEN]; char name[NSLABEL_NAME_LEN]; le32 flags; le16 nlabel; le16 position; le64 isetcookie; le64 lbasize; le64 dpa; le64 rawsize; le32 slot; /* * Accessing fields past this point should be gated by a * namespace_label_has() check. */ u8 align; u8 reserved[3]; char type_guid[NSLABEL_UUID_LEN]; char abstraction_guid[NSLABEL_UUID_LEN]; u8 reserved2[88]; le64 checksum; }; #define BTT_SIG_LEN 16 #define BTT_SIG "BTT_ARENA_INFO\0" #define MAP_TRIM_SHIFT 31 #define MAP_ERR_SHIFT 30 #define MAP_LBA_MASK (~((1 << MAP_TRIM_SHIFT) | (1 << MAP_ERR_SHIFT))) #define MAP_ENT_NORMAL 0xC0000000 #define ARENA_MIN_SIZE (1UL << 24) /* 16 MB */ #define ARENA_MAX_SIZE (1ULL << 39) /* 512 GB */ #define BTT_INFO_SIZE 4096 #define IB_FLAG_ERROR_MASK 0x00000001 #define LOG_GRP_SIZE sizeof(struct log_group) #define LOG_ENT_SIZE sizeof(struct log_entry) #define BTT_NUM_OFFSETS 2 #define BTT1_START_OFFSET 4096 #define BTT2_START_OFFSET 0 struct log_entry { le32 lba; le32 old_map; le32 new_map; le32 seq; }; /* * A log group represents one log 'lane', and consists of four log entries. * Two of the four entries are valid entries, and the remaining two are * padding. Due to an old bug in the padding location, we need to perform a * test to determine the padding scheme being used, and use that scheme * thereafter. * * In kernels prior to 4.15, 'log group' would have actual log entries at * indices (0, 2) and padding at indices (1, 3), where as the correct/updated * format has log entries at indices (0, 1) and padding at indices (2, 3). * * Old (pre 4.15) format: * +-----------------+-----------------+ * | ent[0] | ent[1] | * | 16B | 16B | * | lba/old/new/seq | pad | * +-----------------------------------+ * | ent[2] | ent[3] | * | 16B | 16B | * | lba/old/new/seq | pad | * +-----------------+-----------------+ * * New format: * +-----------------+-----------------+ * | ent[0] | ent[1] | * | 16B | 16B | * | lba/old/new/seq | lba/old/new/seq | * +-----------------------------------+ * | ent[2] | ent[3] | * | 16B | 16B | * | pad | pad | * +-----------------+-----------------+ * * We detect during start-up which format is in use, and set * arena->log_index[(0, 1)] with the detected format. */ struct log_group { struct log_entry ent[4]; }; struct btt_sb { u8 signature[BTT_SIG_LEN]; u8 uuid[16]; u8 parent_uuid[16]; le32 flags; le16 version_major; le16 version_minor; le32 external_lbasize; le32 external_nlba; le32 internal_lbasize; le32 internal_nlba; le32 nfree; le32 infosize; le64 nextoff; le64 dataoff; le64 mapoff; le64 logoff; le64 info2off; u8 padding[3968]; le64 checksum; }; struct free_entry { u32 block; u8 sub; u8 seq; }; struct arena_map { struct btt_sb *info; size_t info_len; void *data; size_t data_len; u32 *map; size_t map_len; struct log_group *log; size_t log_len; struct btt_sb *info2; size_t info2_len; }; #define PFN_SIG_LEN 16 #define PFN_SIG "NVDIMM_PFN_INFO\0" #define DAX_SIG "NVDIMM_DAX_INFO\0" enum pfn_mode { PFN_MODE_NONE, PFN_MODE_RAM, PFN_MODE_PMEM, }; struct pfn_sb { u8 signature[PFN_SIG_LEN]; u8 uuid[16]; u8 parent_uuid[16]; le32 flags; le16 version_major; le16 version_minor; le64 dataoff; /* relative to namespace_base + start_pad */ le64 npfns; le32 mode; /* minor-version-1 additions for section alignment */ le32 start_pad; le32 end_trunc; /* minor-version-2 record the base alignment of the mapping */ le32 align; /* minor-version-3 guarantee the padding and flags are zero */ /* minor-version-4 record the page size and struct page size */ le32 page_size; le16 page_struct_size; u8 padding[3994]; le64 checksum; }; union info_block { struct pfn_sb pfn_sb; struct btt_sb btt_sb; }; static inline bool verify_infoblock_checksum(union info_block *sb) { uint64_t sum; le64 sum_save; BUILD_BUG_ON(sizeof(union info_block) != SZ_4K); /* all infoblocks share the btt_sb layout for checksum */ sum_save = sb->btt_sb.checksum; sb->btt_sb.checksum = 0; sum = fletcher64(&sb->btt_sb, sizeof(*sb), 1); if (sum != sum_save) return false; /* restore the checksum in the buffer */ sb->btt_sb.checksum = sum_save; return true; } #endif /* __NDCTL_NAMESPACE_H__ */ ndctl-81/ndctl/ndctl-monitor.service000066400000000000000000000002061476737544500177130ustar00rootroot00000000000000[Unit] Description=Ndctl Monitor Daemon [Service] Type=simple ExecStart=/usr/bin/ndctl monitor [Install] WantedBy=multi-user.target ndctl-81/ndctl/ndctl.c000066400000000000000000000076031476737544500150200ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2015-2020 Intel Corporation. All rights reserved. /* originally copied from perf and git */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include static const char ndctl_usage_string[] = "ndctl [--version] [--help] COMMAND [ARGS]"; static const char ndctl_more_info_string[] = "See 'ndctl help COMMAND' for more information on a specific command.\n" " ndctl --list-cmds to see all available commands"; static int cmd_version(int argc, const char **argv, struct ndctl_ctx *ctx) { printf("%s\n", VERSION); return 0; } static int cmd_help(int argc, const char **argv, struct ndctl_ctx *ctx) { const char * const builtin_help_subcommands[] = { "enable-region", "disable-region", "zero-labels", "enable-namespace", "disable-namespace", NULL }; struct option builtin_help_options[] = { OPT_END(), }; const char *builtin_help_usage[] = { "ndctl help [command]", NULL }; argc = parse_options_subcommand(argc, argv, builtin_help_options, builtin_help_subcommands, builtin_help_usage, 0); if (!argv[0]) { printf("\n usage: %s\n\n", ndctl_usage_string); printf("\n %s\n\n", ndctl_more_info_string); return 0; } return help_show_man_page(argv[0], "ndctl", "NDCTL_MAN_VIEWER"); } static struct cmd_struct commands[] = { { "version", { cmd_version } }, { "create-nfit", { cmd_create_nfit } }, { "enable-namespace", { cmd_enable_namespace } }, { "disable-namespace", { cmd_disable_namespace } }, { "create-namespace", { cmd_create_namespace } }, { "destroy-namespace", { cmd_destroy_namespace } }, { "read-infoblock", { cmd_read_infoblock } }, { "write-infoblock", { cmd_write_infoblock } }, { "check-namespace", { cmd_check_namespace } }, { "clear-errors", { cmd_clear_errors } }, { "enable-region", { cmd_enable_region } }, { "disable-region", { cmd_disable_region } }, { "enable-dimm", { cmd_enable_dimm } }, { "disable-dimm", { cmd_disable_dimm } }, { "zero-labels", { cmd_zero_labels } }, { "read-labels", { cmd_read_labels } }, { "write-labels", { cmd_write_labels } }, { "init-labels", { cmd_init_labels } }, { "check-labels", { cmd_check_labels } }, { "inject-error", { cmd_inject_error } }, { "update-firmware", { cmd_update_firmware } }, { "inject-smart", { cmd_inject_smart } }, { "wait-scrub", { cmd_wait_scrub } }, { "activate-firmware", { cmd_activate_firmware } }, { "start-scrub", { cmd_start_scrub } }, { "setup-passphrase", { cmd_setup_passphrase } }, { "update-passphrase", { cmd_update_passphrase } }, { "remove-passphrase", { cmd_remove_passphrase } }, { "freeze-security", { cmd_freeze_security } }, { "sanitize-dimm", { cmd_sanitize_dimm } }, #ifdef ENABLE_KEYUTILS { "load-keys", { cmd_load_keys } }, #endif { "wait-overwrite", { cmd_wait_overwrite } }, { "list", { cmd_list } }, { "monitor", { cmd_monitor } }, { "help", { cmd_help } }, #ifdef ENABLE_TEST { "test", { cmd_test } }, #endif #ifdef ENABLE_DESTRUCTIVE { "bat", { cmd_bat } }, #endif }; int main(int argc, const char **argv) { struct ndctl_ctx *ctx; int rc; /* Look for flags.. */ argv++; argc--; main_handle_options(&argv, &argc, ndctl_usage_string, commands, ARRAY_SIZE(commands)); if (argc > 0) { if (!prefixcmp(argv[0], "--")) argv[0] += 2; } else { /* The user didn't specify a command; give them help */ printf("\n usage: %s\n\n", ndctl_usage_string); printf("\n %s\n\n", ndctl_more_info_string); goto out; } rc = ndctl_new(&ctx); if (rc) goto out; main_handle_internal_command(argc, argv, ctx, commands, ARRAY_SIZE(commands), PROG_NDCTL); ndctl_unref(ctx); fprintf(stderr, "Unknown command: '%s'\n", argv[0]); out: return 1; } ndctl-81/ndctl/ndctl.conf000066400000000000000000000012611476737544500155150ustar00rootroot00000000000000# This is the default ndctl configuration file. It contains the # configuration directives that give ndctl instructions. # Ndctl supports multiple configuration files. All files with the # .conf suffix under {sysconfdir}/ndctl.conf.d will be regarded as # valid ndctl configuration files. # In this file, lines starting with a hash (#) are comments. # The configurations should be in a [section] and follow = # style. Multiple space-separated values are allowed, but except the # following characters: : ? / \ % " ' $ & ! * { } [ ] ( ) = < > @ [core] # The values in [core] section work for all ndctl sub commands. # dimm = all # bus = all # region = all # namespace = all ndctl-81/ndctl/ndctl.h000066400000000000000000000150021476737544500150150ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1 */ /* Copyright (C) 2014-2020, Intel Corporation. All rights reserved. */ #ifndef __NDCTL_H__ #define __NDCTL_H__ #ifndef ARRAY_SIZE #include #endif #include #include #include struct nd_cmd_dimm_flags { __u32 status; __u32 flags; } __attribute__((packed)); struct nd_cmd_get_config_size { __u32 status; __u32 config_size; __u32 max_xfer; } __attribute__((packed)); struct nd_cmd_get_config_data_hdr { __u32 in_offset; __u32 in_length; __u32 status; __u8 out_buf[0]; } __attribute__((packed)); struct nd_cmd_set_config_hdr { __u32 in_offset; __u32 in_length; __u8 in_buf[0]; } __attribute__((packed)); struct nd_cmd_vendor_hdr { __u32 opcode; __u32 in_length; __u8 in_buf[0]; } __attribute__((packed)); struct nd_cmd_vendor_tail { __u32 status; __u32 out_length; __u8 out_buf[0]; } __attribute__((packed)); struct nd_cmd_ars_cap { __u64 address; __u64 length; __u32 status; __u32 max_ars_out; __u32 clear_err_unit; __u16 flags; __u16 reserved; } __attribute__((packed)); struct nd_cmd_ars_start { __u64 address; __u64 length; __u16 type; __u8 flags; __u8 reserved[5]; __u32 status; __u32 scrub_time; } __attribute__((packed)); struct nd_cmd_ars_status { __u32 status; __u32 out_length; __u64 address; __u64 length; __u64 restart_address; __u64 restart_length; __u16 type; __u16 flags; __u32 num_records; struct nd_ars_record { __u32 handle; __u32 reserved; __u64 err_address; __u64 length; } __attribute__((packed)) records[]; } __attribute__((packed)); struct nd_cmd_clear_error { __u64 address; __u64 length; __u32 status; __u8 reserved[4]; __u64 cleared; } __attribute__((packed)); enum { ND_CMD_IMPLEMENTED = 0, /* bus commands */ ND_CMD_ARS_CAP = 1, ND_CMD_ARS_START = 2, ND_CMD_ARS_STATUS = 3, ND_CMD_CLEAR_ERROR = 4, /* per-dimm commands */ ND_CMD_SMART = 1, ND_CMD_SMART_THRESHOLD = 2, ND_CMD_DIMM_FLAGS = 3, ND_CMD_GET_CONFIG_SIZE = 4, ND_CMD_GET_CONFIG_DATA = 5, ND_CMD_SET_CONFIG_DATA = 6, ND_CMD_VENDOR_EFFECT_LOG_SIZE = 7, ND_CMD_VENDOR_EFFECT_LOG = 8, ND_CMD_VENDOR = 9, ND_CMD_CALL = 10, }; enum { ND_ARS_VOLATILE = 1, ND_ARS_PERSISTENT = 2, ND_ARS_RETURN_PREV_DATA = 1 << 1, ND_CONFIG_LOCKED = 1, }; static __inline__ const char *nvdimm_bus_cmd_name(unsigned cmd) { static const char * const names[] = { [ND_CMD_ARS_CAP] = "ars_cap", [ND_CMD_ARS_START] = "ars_start", [ND_CMD_ARS_STATUS] = "ars_status", [ND_CMD_CLEAR_ERROR] = "clear_error", [ND_CMD_CALL] = "cmd_call", }; if (cmd < ARRAY_SIZE(names) && names[cmd]) return names[cmd]; return "unknown"; } static __inline__ const char *nvdimm_cmd_name(unsigned cmd) { static const char * const names[] = { [ND_CMD_SMART] = "smart", [ND_CMD_SMART_THRESHOLD] = "smart_thresh", [ND_CMD_DIMM_FLAGS] = "flags", [ND_CMD_GET_CONFIG_SIZE] = "get_size", [ND_CMD_GET_CONFIG_DATA] = "get_data", [ND_CMD_SET_CONFIG_DATA] = "set_data", [ND_CMD_VENDOR_EFFECT_LOG_SIZE] = "effect_size", [ND_CMD_VENDOR_EFFECT_LOG] = "effect_log", [ND_CMD_VENDOR] = "vendor", [ND_CMD_CALL] = "cmd_call", }; if (cmd < ARRAY_SIZE(names) && names[cmd]) return names[cmd]; return "unknown"; } #define ND_IOCTL 'N' #define ND_IOCTL_DIMM_FLAGS _IOWR(ND_IOCTL, ND_CMD_DIMM_FLAGS,\ struct nd_cmd_dimm_flags) #define ND_IOCTL_GET_CONFIG_SIZE _IOWR(ND_IOCTL, ND_CMD_GET_CONFIG_SIZE,\ struct nd_cmd_get_config_size) #define ND_IOCTL_GET_CONFIG_DATA _IOWR(ND_IOCTL, ND_CMD_GET_CONFIG_DATA,\ struct nd_cmd_get_config_data_hdr) #define ND_IOCTL_SET_CONFIG_DATA _IOWR(ND_IOCTL, ND_CMD_SET_CONFIG_DATA,\ struct nd_cmd_set_config_hdr) #define ND_IOCTL_VENDOR _IOWR(ND_IOCTL, ND_CMD_VENDOR,\ struct nd_cmd_vendor_hdr) #define ND_IOCTL_ARS_CAP _IOWR(ND_IOCTL, ND_CMD_ARS_CAP,\ struct nd_cmd_ars_cap) #define ND_IOCTL_ARS_START _IOWR(ND_IOCTL, ND_CMD_ARS_START,\ struct nd_cmd_ars_start) #define ND_IOCTL_ARS_STATUS _IOWR(ND_IOCTL, ND_CMD_ARS_STATUS,\ struct nd_cmd_ars_status) #define ND_IOCTL_CLEAR_ERROR _IOWR(ND_IOCTL, ND_CMD_CLEAR_ERROR,\ struct nd_cmd_clear_error) #define ND_DEVICE_DIMM 1 /* nd_dimm: container for "config data" */ #define ND_DEVICE_REGION_PMEM 2 /* nd_region: (parent of PMEM namespaces) */ #define ND_DEVICE_REGION_BLK 3 /* nd_region: (parent of BLK namespaces) */ #define ND_DEVICE_NAMESPACE_IO 4 /* legacy persistent memory */ #define ND_DEVICE_NAMESPACE_PMEM 5 /* PMEM namespace (may alias with BLK) */ #define ND_DEVICE_NAMESPACE_BLK 6 /* BLK namespace (may alias with PMEM) */ #define ND_DEVICE_DAX_PMEM 7 /* Device DAX interface to pmem */ enum nd_driver_flags { ND_DRIVER_DIMM = 1 << ND_DEVICE_DIMM, ND_DRIVER_REGION_PMEM = 1 << ND_DEVICE_REGION_PMEM, ND_DRIVER_REGION_BLK = 1 << ND_DEVICE_REGION_BLK, ND_DRIVER_NAMESPACE_IO = 1 << ND_DEVICE_NAMESPACE_IO, ND_DRIVER_NAMESPACE_PMEM = 1 << ND_DEVICE_NAMESPACE_PMEM, ND_DRIVER_NAMESPACE_BLK = 1 << ND_DEVICE_NAMESPACE_BLK, ND_DRIVER_DAX_PMEM = 1 << ND_DEVICE_DAX_PMEM, }; #ifdef PAGE_SIZE enum { ND_MIN_NAMESPACE_SIZE = PAGE_SIZE, }; #else #define ND_MIN_NAMESPACE_SIZE ((unsigned) sysconf(_SC_PAGESIZE)) #endif enum ars_masks { ARS_STATUS_MASK = 0x0000FFFF, ARS_EXT_STATUS_SHIFT = 16, }; /* * struct nd_cmd_pkg * * is a wrapper to a quasi pass thru interface for invoking firmware * associated with nvdimms. * * INPUT PARAMETERS * * nd_family corresponds to the firmware (e.g. DSM) interface. * * nd_command are the function index advertised by the firmware. * * nd_size_in is the size of the input parameters being passed to firmware * * OUTPUT PARAMETERS * * nd_fw_size is the size of the data firmware wants to return for * the call. If nd_fw_size is greater than size of nd_size_out, only * the first nd_size_out bytes are returned. */ struct nd_cmd_pkg { __u64 nd_family; /* family of commands */ __u64 nd_command; __u32 nd_size_in; /* INPUT: size of input args */ __u32 nd_size_out; /* INPUT: size of payload */ __u32 nd_reserved2[9]; /* reserved must be zero */ __u32 nd_fw_size; /* OUTPUT: size fw wants to return */ unsigned char nd_payload[]; /* Contents of call */ }; /* These NVDIMM families represent pre-standardization command sets */ #define NVDIMM_FAMILY_INTEL 0 #define NVDIMM_FAMILY_HPE1 1 #define NVDIMM_FAMILY_HPE2 2 #define NVDIMM_FAMILY_MSFT 3 #define NVDIMM_FAMILY_HYPERV 4 #define NVDIMM_FAMILY_PAPR 5 #define ND_IOCTL_CALL _IOWR(ND_IOCTL, ND_CMD_CALL,\ struct nd_cmd_pkg) #endif /* __NDCTL_H__ */ ndctl-81/ndctl/region.c000066400000000000000000000071771476737544500152050ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2015-2020 Intel Corporation. All rights reserved. #include #include #include #include #include "action.h" #include #include #include "filter.h" static struct { const char *bus; const char *type; } param; static const struct option region_options[] = { OPT_STRING('b', "bus", ¶m.bus, "bus-id", " must be on a bus with an id/provider of "), OPT_STRING('t', "type", ¶m.type, "region-type", " must be of the specified type"), OPT_END(), }; static const char *parse_region_options(int argc, const char **argv, char *xable_usage) { const char * const u[] = { xable_usage, NULL }; int i; argc = parse_options(argc, argv, region_options, u, 0); if (argc == 0) error("specify a specific region id to act on, or \"all\"\n"); for (i = 1; i < argc; i++) error("unknown extra parameter \"%s\"\n", argv[i]); if (argc == 0 || argc > 1) { usage_with_options(u, region_options); return NULL; /* we won't return from usage_with_options() */ } if (param.type) { if (strcmp(param.type, "pmem") == 0) /* pass */; else if (strcmp(param.type, "blk") == 0) /* pass */; else { error("unknown region type '%s', should be 'pmem' or 'blk'\n", param.type); usage_with_options(u, region_options); return NULL; } } return argv[0]; } static int region_action(struct ndctl_region *region, enum device_action mode) { struct ndctl_namespace *ndns; int rc = 0; switch (mode) { case ACTION_ENABLE: rc = ndctl_region_enable(region); break; case ACTION_DISABLE: ndctl_namespace_foreach(region, ndns) { rc = ndctl_namespace_disable_safe(ndns); if (rc) return rc; } rc = ndctl_region_disable_invalidate(region); break; default: break; } return rc; } static int do_xable_region(const char *region_arg, enum device_action mode, struct ndctl_ctx *ctx) { int rc = -ENXIO, success = 0; struct ndctl_region *region; struct ndctl_bus *bus; if (!region_arg) goto out; ndctl_bus_foreach(ctx, bus) { if (!util_bus_filter(bus, param.bus)) continue; ndctl_region_foreach(bus, region) { const char *type = ndctl_region_get_type_name(region); if (param.type && strcmp(param.type, type) != 0) continue; if (!util_region_filter(region, region_arg)) continue; if (region_action(region, mode) == 0) success++; } } rc = success; out: param.bus = NULL; return rc; } int cmd_disable_region(int argc, const char **argv, struct ndctl_ctx *ctx) { char *xable_usage = "ndctl disable-region []"; const char *region = parse_region_options(argc, argv, xable_usage); int disabled = do_xable_region(region, ACTION_DISABLE, ctx); if (disabled < 0) { fprintf(stderr, "error disabling regions: %s\n", strerror(-disabled)); return disabled; } else if (disabled == 0) { fprintf(stderr, "disabled 0 regions\n"); return 0; } else { fprintf(stderr, "disabled %d region%s\n", disabled, disabled > 1 ? "s" : ""); return 0; } } int cmd_enable_region(int argc, const char **argv, struct ndctl_ctx *ctx) { char *xable_usage = "ndctl enable-region []"; const char *region = parse_region_options(argc, argv, xable_usage); int enabled = do_xable_region(region, ACTION_ENABLE, ctx); if (enabled < 0) { fprintf(stderr, "error enabling regions: %s\n", strerror(-enabled)); return enabled; } else if (enabled == 0) { fprintf(stderr, "enabled 0 regions\n"); return 0; } else { fprintf(stderr, "enabled %d region%s\n", enabled, enabled > 1 ? "s" : ""); return 0; } } ndctl-81/ndctl/test.c000066400000000000000000000025341476737544500146710ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2015-2020 Intel Corporation. All rights reserved. #include #include #include #include #include static char *result(int rc) { if (rc == 77) return "SKIP"; else if (rc) return "FAIL"; else return "PASS"; } int cmd_test(int argc, const char **argv, struct ndctl_ctx *ctx) { struct ndctl_test *test; int loglevel = LOG_DEBUG, i, rc; const char * const u[] = { "ndctl test []", NULL }; bool force = false; const struct option options[] = { OPT_INTEGER('l', "loglevel", &loglevel, "set the log level (default LOG_DEBUG)"), OPT_BOOLEAN('f', "force", &force, "force run all tests regardless of required kernel"), OPT_END(), }; argc = parse_options(argc, argv, options, u, 0); for (i = 0; i < argc; i++) error("unknown parameter \"%s\"\n", argv[i]); if (argc) usage_with_options(u, options); if (force) test = ndctl_test_new(UINT_MAX); else test = ndctl_test_new(0); if (!test) return EXIT_FAILURE; rc = test_libndctl(loglevel, test, ctx); fprintf(stderr, "test-libndctl: %s\n", result(rc)); if (rc && rc != 77) return rc; rc = test_dsm_fail(loglevel, test, ctx); fprintf(stderr, "test-dsm-fail: %s\n", result(rc)); if (rc && rc != 77) return rc; return ndctl_test_result(test, rc); } ndctl-81/rhel/000077500000000000000000000000001476737544500133705ustar00rootroot00000000000000ndctl-81/rhel/meson.build000066400000000000000000000010351476737544500155310ustar00rootroot00000000000000rhel_spec1 = vcs_tag( input : '../ndctl.spec.in', output : 'ndctl.spec.in', command: vcs_tagger, replace_string : 'VERSION', ) rhel_spec2 = custom_target('ndctl.spec', command : [ 'sed', '-e', 's,DAX_DNAME,daxctl-devel,g', '-e', 's,CXL_DNAME,cxl-devel,g', '-e', 's,DNAME,ndctl-devel,g', '-e', '/^%defattr.*/d', '-e', 's,DAX_LNAME,daxctl-libs,g', '-e', 's,CXL_LNAME,cxl-libs,g', '-e', 's,LNAME,ndctl-libs,g', '@INPUT@' ], input : rhel_spec1, output : 'ndctl.spec', capture : true, ) ndctl-81/rpmbuild.sh000077500000000000000000000007471476737544500146230ustar00rootroot00000000000000#!/bin/bash spec=${1:-$(dirname $0)/rhel/ndctl.spec)} pushd $(dirname $0) >/dev/null [ ! -d ~/rpmbuild/SOURCES ] && echo "rpmdev tree not found" && exit 1 if ./git-version | grep -q dirty; then echo "Uncommitted changes detected, commit or undo them to proceed" git status -uno --short exit 1 fi if [ ! -f $spec ]; then meson compile -C build rhel/ndctl.spec || exit spec=$(dirname $0)/build/rhel/ndctl.spec fi ./make-git-snapshot.sh popd > /dev/null rpmbuild --nocheck -ba $spec ndctl-81/scripts/000077500000000000000000000000001476737544500141255ustar00rootroot00000000000000ndctl-81/scripts/daxctl-qemu-hmat-setup000077500000000000000000000157431476737544500203760ustar00rootroot00000000000000#!/bin/bash -e # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2019-2020 Intel Corporation. All rights reserved. KERNEL=${KERNEL:-$(uname -r)} ROOT_IMAGE=${ROOT_IMAGE:-root.img} INITIATORS=4 TARGETS=128 TARGET_SIZE_MB=128 TARGET_SIZE="$((TARGET_SIZE_MB * 1024 * 1024))" QEMU_MEM="$((TARGET_SIZE_MB * TARGETS))" for i in $(seq 0 $((TARGETS - 1))); do qemu-img create -f raw "dimm-$i" "${TARGET_SIZE}" done; # Address Range Structures cat < hmat.dsl Signature : "HMAT" [Heterogeneous Memory Attributes Table] Table Length : 00000200 Revision : 01 Checksum : F4 Oem ID : "BOCHS " Oem Table ID : "BXPCHMAT" Oem Revision : 00000001 Asl Compiler ID : "INTL" Asl Compiler Revision : 20170929 Reserved : 00000000 Structure Type : 0000 [Memory Subystem Address Range] Reserved : 0000 Length : 00000028 Flags (decoded below) : 0003 Processor Proximity Domain Valid : 1 Memory Proximity Domain Valid : 1 Reservation Hint : 0 Reserved1 : 0000 Processor Proximity Domain : 00000000 Memory Proximity Domain : 00000000 Reserved2 : 00000000 Physical Address Range Base : 0000000000000000 Physical Address Range Size : 00000000000A0000 Structure Type : 0000 [Memory Subystem Address Range] Reserved : 0000 Length : 00000028 Flags (decoded below) : 0003 Processor Proximity Domain Valid : 1 Memory Proximity Domain Valid : 1 Reservation Hint : 0 Reserved1 : 0000 Processor Proximity Domain : 00000000 Memory Proximity Domain : 00000000 Reserved2 : 00000000 Physical Address Range Base : 0000000000100000 Physical Address Range Size : 0000000007F00000 EOF for i in $(seq 1 $((TARGETS - 1))); do BASE=$(printf "%016x" $((0x8000000 * i))) cat <> hmat.dsl Structure Type : 0000 [Memory Subystem Address Range] Reserved : 0000 Length : 00000028 Flags (decoded below) : 0003 Processor Proximity Domain Valid : 1 Memory Proximity Domain Valid : 1 Reservation Hint : 0 Reserved1 : 0000 Processor Proximity Domain : $(printf "%08x" $((i % INITIATORS))) Memory Proximity Domain : $(printf "%08x" "${i}") Reserved2 : 00000000 Physical Address Range Base : ${BASE} Physical Address Range Size : 0000000008000000 EOF done # System Locality Latency TABLE_SIZE="$(printf "%08x" $((40 + 4 * INITIATORS + 4 * TARGETS + 2 * INITIATORS * TARGETS)))" cat <> hmat.dsl Structure Type : 0001 [System Locality Latency and Bandwidth Information] Reserved : 0000 Length : ${TABLE_SIZE} Flags (decoded below) : 00 Memory Hierarchy : 0 Data Type : 00 Reserved1 : 0000 Initiator Proximity Domains # : $(printf "%08x" ${INITIATORS}) Target Proximity Domains # : $(printf "%08x" ${TARGETS}) Reserved2 : 00000000 Entry Base Unit : 0000000000000001 EOF for i in $(seq 0 $((INITIATORS - 1))); do cat <> hmat.dsl Initiator Proximity Domain List : $(printf "%08x" "${i}") EOF done for i in $(seq 0 $((TARGETS - 1))); do cat <> hmat.dsl Target Proximity Domain List : $(printf "%08x" "${i}") EOF done LOCAL_START=0x10 REMOTE_START=0x20 for i in $(seq 0 $((INITIATORS - 1))); do for j in $(seq 0 $((TARGETS - 1))); do if [ "$((j % INITIATORS))" -eq "${i}" ]; then cat <> hmat.dsl Entry : $(printf "%04x" $((LOCAL_START + j * 10))) EOF else cat <> hmat.dsl Entry : $(printf "%04x" $((REMOTE_START + j * 10))) EOF fi done done # System Locality Bandwidth TABLE_SIZE=$(printf "%08x" $((40 + 4 * INITIATORS + 4 * TARGETS + 2 * INITIATORS * TARGETS))) cat <> hmat.dsl Structure Type : 0001 [System Locality Latency and Bandwidth Information] Reserved : 0000 Length : ${TABLE_SIZE} Flags (decoded below) : 00 Memory Hierarchy : 0 Data Type : 03 Reserved1 : 0000 Initiator Proximity Domains # : $(printf "%08x" ${INITIATORS}) Target Proximity Domains # : $(printf "%08x" ${TARGETS}) Reserved2 : 00000000 Entry Base Unit : 0000000000000400 EOF for i in $(seq 0 $((INITIATORS - 1))); do cat <> hmat.dsl Initiator Proximity Domain List : $(printf "%08x" "${i}") EOF done for i in $(seq 0 $((TARGETS - 1))); do cat <> hmat.dsl Target Proximity Domain List : $(printf "%08x" "${i}") EOF done LOCAL_START=0x2000 REMOTE_START=0x1000 for i in $(seq 0 $((INITIATORS - 1))); do for j in $(seq 0 $((TARGETS - 1))); do if [ "$((j % INITIATORS))" -eq "${i}" ]; then cat <> hmat.dsl Entry : $(printf "%04x" $((LOCAL_START - j * 32))) EOF else cat <> hmat.dsl Entry : $(printf "%04x" $((REMOTE_START - j * 32))) EOF fi done done # Side Caches for i in $(seq 0 ${TARGETS}); do cat <> hmat.dsl Structure Type : 0002 [Memory Side Cache Information Structure] Reserved : 0000 Length : 00000020 Memory Proximity Domain : $(printf "%08x" "${i}") Reserved1 : 00000000 Memory Side Cache Size : 0000000000100000 Cache Attributes (decoded below) : 01001113 Total Cache Levels : 1 Cache Level : 1 Cache Associativity : 1 Write Policy : 1 Cache Line Size : 0100 Reserved2 : 0000 SMBIOS Handle # : 0000 EOF done # Generate injected initrd iasl ./*dsl mkdir -p kernel/firmware/acpi/ rm -f kernel/firmware/acpi/*.aml hmat-initramfs cp ./*aml kernel/firmware/acpi/ find kernel | cpio -c -o > hmat-initramfs cat "/boot/initramfs-${KERNEL}.img" >> hmat-initramfs # Build and evaluate QEMU command line QEMU="qemu-system-x86_64 -m ${QEMU_MEM} -smp 8,sockets=${INITIATORS},maxcpus=8 -machine pc,accel=kvm " QEMU+="-enable-kvm -display none -nographic -snapshot -hda ${ROOT_IMAGE} " QEMU+="-kernel /boot/vmlinuz-${KERNEL} -initrd ./hmat-initramfs " QEMU+="-append \"console=tty0 console=ttyS0 root=/dev/sda rw\" " for i in $(seq 0 $((TARGETS - 1))); do QEMU+="-object memory-backend-file,id=mem${i},share,mem-path=dimm-${i},size=${TARGET_SIZE_MB}M " QEMU+="-numa node,nodeid=${i},memdev=mem${i} " done eval "${QEMU}" ndctl-81/scripts/do_abidiff000077500000000000000000000044441476737544500161270ustar00rootroot00000000000000#!/bin/bash -e range="$*" old="${range%%..*}" new="${range##*..}" err() { echo "$1" exit 1 } build_rpm() { local cur=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) local ref="$1" local version="" # prepare ndctl tree rm -rf results_ndctl build git checkout -b rel_${ref} $ref meson setup build meson compile -C build rhel/ndctl.spec cp build/rhel/ndctl.spec . # build and copy RPMs version="$(./git-version)" release="f$(basename $(readlink -f /etc/mock/default.cfg) | cut -d- -f2)" git archive --format=tar --prefix="ndctl-${version}/" HEAD | gzip > ndctl-${version}.tar.gz fedpkg --release $release --name=ndctl mockbuild [ "$?" -eq 0 ] || err "error building $ref" mkdir -p release/rel_${ref}/ cp results_ndctl/*/*/*.x86_64.rpm release/rel_${ref}/ # restore ndctl branch and cleanup git checkout $cur git branch -D rel_${ref} rm ndctl-${version}.tar.gz rm ndctl-${version}*.src.rpm rm -rf results_ndctl rm -f ndctl.spec } do_diff() { local pkg="$1" local old_base="$(find . -regex "./release/rel_${old}/${pkg}[-cli]*-[0-9]+.*" | head -1)" local new_base="$(find . -regex "./release/rel_${new}/${pkg}[-cli]*-[0-9]+.*" | head -1)" local old_dev="$(find . -regex "./release/rel_${old}/${pkg}-devel-[0-9]+.*" | head -1)" local new_dev="$(find . -regex "./release/rel_${new}/${pkg}-devel-[0-9]+.*" | head -1)" local old_lib="$(find . -regex "./release/rel_${old}/${pkg}-libs-[0-9]+.*" | head -1)" local new_lib="$(find . -regex "./release/rel_${new}/${pkg}-libs-[0-9]+.*" | head -1)" [ -n "$pkg" ] || err "specify a package for diff (ndctl, daxctl, cxl)" [ -n "$old_base" ] || err "$pkg: old_base empty, possible build failure" [ -n "$new_base" ] || err "$pkg: new_base empty, possible build failure" abipkgdiff --dso-only --no-added-syms --harmless --drop-private-types \ --devel1 "$old_dev" --devel2 "$new_dev" \ "$old_base" "$new_base" abipkgdiff --no-added-syms --harmless --drop-private-types \ --devel1 "$old_dev" --devel2 "$new_dev" \ "$old_lib" "$new_lib" } [ -e "COPYING" ] || err "Run from the top level of an ndctl tree" if ! command -v "abipkgdiff" >/dev/null; then err "missing abipkgdiff. Please install libabigail" fi rm -rf release/rel* build_rpm $old > release/buildlog_$old 2>&1 build_rpm $new > release/buildlog_$new 2>&1 do_diff ndctl do_diff daxctl do_diff cxl ndctl-81/scripts/docsurgeon000077500000000000000000000136201476737544500162250ustar00rootroot00000000000000#!/bin/bash -eE this_script="docsurgeon" script_dir="$(cd "$(dirname "$(readlink -e "${BASH_SOURCE[0]}")")" && pwd)" env_file="${script_dir}/.env" if [ -e "$env_file" ]; then # shellcheck source=.env . "$env_file" fi sources_file="${script_dir}/.sources" parser_generator="${script_dir}/${this_script}_parser_generator.m4" parser_lib="${script_dir}/${this_script}_parser.sh" if [ ! -e "$parser_lib" ] || [ "$parser_generator" -nt "$parser_lib" ]; then if command -V argbash > /dev/null; then argbash --strip user-content "$parser_generator" -o "$parser_lib" else echo "error: please install argbash" >&2 exit 1 fi fi if [[ $1 != "bin" ]]; then # shellcheck source=docsurgeon_parser.sh . "${script_dir}/${this_script}_parser.sh" || { echo "Couldn't find $parser_lib" >&2; exit 1; } fi # some script defaults - override using '.env' docbase="Documentation" copyright_cli="// SPDX-License-Identifier: GPL-2.0" copyright_footer_cli="include::../copyright.txt[]" copyright_lib="// SPDX-License-Identifier: LGPL-2.0" copyright_footer_lib="include::../../copyright.txt[]" # List of files we're creating, to be edited/renamed later # This starts out blank, and is filled in as we go by gen_*() functions declare -a outfiles cleanup() { if [ ${#outfiles[@]} -gt 0 ]; then rm -f "${outfiles[@]}" fi set +x } trap cleanup EXIT auto_detect_params() { fs="" module="" section="" # if module and section were explicitly specified, respect them if [[ $_arg_module ]] && [[ $_arg_section ]]; then return fi # check if names are self-consistent, and determine 'fs' for name in ${_arg_name[@]}; do if [[ ! $fs ]]; then if [[ $name == *-* ]]; then fs="-" elif [[ $name == *_* ]]; then fs="_" else # can't autodetect section return fi fi if [[ $fs == "-" ]] && [[ $name == *_* ]]; then die "can't auto-detect params with mixed-style names" fi if [[ $fs == "_" ]] && [[ $name == *-* ]]; then die "can't auto-detect params with mixed-style names" fi done # try to detect module name for name in ${_arg_name[@]}; do str=${name%%$fs*} if [[ $module ]]; then if [[ $str != $module ]]; then die "Can't autodetect module because of mixed names ($str and $module)" fi else module="$str" fi done # try to detect section number case "$fs" in -) section=1 ;; _) section=3 ;; *) die "Unknown fs, can't autodetect section number" ;; esac if [[ $module ]]; then _arg_module="$module" fi if [[ $section ]]; then _arg_section="$section" fi } process_options_logic() { if [[ $_arg_debug == "on" ]]; then set -x fi auto_detect_params } gen_underline() { name="$1" char="$2" num="${#name}" printf -v tmpstring "%-${num}s" " " echo "${tmpstring// /$char}" } gen_header() { printf "\n%s\n%s\n" "$1" "$(gen_underline "$1" "=")" } gen_section() { printf "\n%s\n%s\n" "$1" "$(gen_underline "$1" "-")" } gen_section_name() { name="$1" gen_section "NAME" cat <<- EOF $name - EOF } gen_section_synopsis_1() { name="$1" gen_section "SYNOPSIS" cat <<- EOF [verse] '$_arg_module ${name#*-} []' EOF } gen_section_synopsis_3() { name="$1" gen_section "SYNOPSIS" cat <<- EOF [verse] ---- #include <$_arg_module/lib$_arg_module.h> $name(); ---- EOF } gen_section_example_1() { name="$1" gen_section "EXAMPLE" cat <<- EOF ---- # $_arg_module ${name#*-} ---- EOF } gen_section_example_3() { name="$1" gen_section "EXAMPLE" cat <<- EOF See example usage in test/lib$_arg_module.c EOF } gen_section_options_1() { gen_section "OPTIONS" cat << EOF -o:: --option:: Description EOF if [[ $_arg_human_option == "on" ]]; then printf "\n%s\n" "include::human-option.txt[]" fi if [[ $_arg_verbose_option == "on" ]]; then printf "\n%s\n" "include::verbose-option.txt[]" fi } gen_section_seealso_1() { gen_section "SEE ALSO" cat <<- EOF link$_arg_module:$_arg_module-list[$_arg_section], EOF } gen_section_seealso_3() { gen_section "SEE ALSO" cat <<- EOF linklib$_arg_module:${_arg_module}_other_API[$_arg_section], EOF } gen_cli() { name="$1" path="$docbase/$_arg_module" if [ ! -d "$path" ]; then die "Not found: $path" fi tmp="$(mktemp -p "$path" "$name.txt.XXXX")" outfiles+=("$tmp") # Start template generation printf "%s\n" "$copyright_cli" > "$tmp" gen_header "$name($_arg_section)" >> "$tmp" gen_section_name "$name" >> "$tmp" gen_section_synopsis_1 "$name" >> "$tmp" gen_section "DESCRIPTION" >> "$tmp" gen_section_example_1 "$name" >> "$tmp" gen_section_options_1 >> "$tmp" printf "\n%s\n" "$copyright_footer_cli" >> "$tmp" gen_section_seealso_1 >> "$tmp" } gen_lib() { name="$1" path="$docbase/$_arg_module/lib" if [ ! -d "$path" ]; then die "Not found: $path" fi tmp="$(mktemp -p "$path" "$name.txt.XXXX")" outfiles+=("$tmp") # Start template generation printf "%s\n" "$copyright_lib" > "$tmp" gen_header "$name($_arg_section)" >> "$tmp" gen_section_name "$name" >> "$tmp" gen_section_synopsis_3 "$name" >> "$tmp" gen_section "DESCRIPTION" >> "$tmp" gen_section "RETURN VALUE" >> "$tmp" gen_section_example_3 "$name" >> "$tmp" printf "\n%s\n" "$copyright_footer_lib" >> "$tmp" gen_section_seealso_3 >> "$tmp" } gen_man() { name="$1" case "$_arg_section" in 1) gen_cli "$name" ;; 3) gen_lib "$name" ;; *) die "Unknown section: $_arg_section" ;; esac } gen_include() { echo "in gen_include" } main() { process_options_logic cmd="$_arg_command" case "$cmd" in gen-man) for name in ${_arg_name[@]}; do gen_man "$name" done ;; gen-include) for name in ${_arg_name[@]}; do gen_include done ;; *) die "Unknown command: $cmd" ;; esac if [[ $_arg_dump == "on" ]]; then for file in ${outfiles[@]}; do echo "${file##*/}" cat "$file" rm "$file" done elif [ ${#outfiles[@]} -gt 0 ]; then if [[ $_arg_edit = "on" ]]; then vim -p "${outfiles[@]}" fi for file in ${outfiles[@]}; do mv "$file" "${file%.*}" done fi } main "$@" ndctl-81/scripts/docsurgeon_parser_generator.m4000066400000000000000000000025561476737544500221710ustar00rootroot00000000000000#!/bin/bash # m4_ignore( echo "This is just a parsing library template, not the library - pass this file to 'argbash' to fix this." >&2 exit 11 #)Created by argbash-init v2.9.0 # Rearrange the order of options below according to what you would like to see in the help message. # ARG_OPTIONAL_REPEATED([name], [n], [Command or function name to generate a template for.\n Can be repeated for multiple names. ], []) # ARG_OPTIONAL_BOOLEAN([edit], [e], [Edit template files after creation], [on]) # ARG_OPTIONAL_BOOLEAN([debug], [], [Debug script problems (enables set -x)], ) # ARG_OPTIONAL_BOOLEAN([dump], [], [Write generated file to stdout instead of a file], ) # ARG_OPTIONAL_SINGLE([module], [m], [Module (Docs subdir) in which to create the template], []) # ARG_OPTIONAL_SINGLE([section], [s], [man section for which to create the template], []) # ARG_OPTIONAL_BOOLEAN([human-option], [u], [Include the human option in 'OPTIONS'], ) # ARG_OPTIONAL_BOOLEAN([verbose-option], [V], [Include the verbose option in 'OPTIONS'], ) # ARG_POSITIONAL_DOUBLEDASH() # ARG_POSITIONAL_SINGLE([command], [Operation to perform:\n gen-man\n gen-include], []) # ARGBASH_SET_DELIM([ =]) # ARG_OPTION_STACKING([getopt]) # ARG_RESTRICT_VALUES([no-local-options]) # ARG_DEFAULTS_POS # ARG_HELP([Tool to aid in creating and managing man page templates]) # ARG_VERSION([echo "docsurgeon 0.1"]) # ARGBASH_GO ndctl-81/scripts/prepare-release.sh000077500000000000000000000132721476737544500175450ustar00rootroot00000000000000#!/bin/bash -e # Arguments: # fix - fixup release instead of a full release # ignore_rev - ignore the check for _REVISION in libtool versioning checks # Notes: # - Checkout to the appropriate branch beforehand # main - for major release # ndctl-xx.y - for fixup release # This is important for generating the shortlog # - Add a temporary commit that updates the libtool versions as needed. # This will later become the release commit. Use --amend to add in the # git-version update and the message body. # Pre-reqs: # - libabigail (for abipkgdiff) # - fedpkg (for mock build) # TODO # - auto generate a release commit/tag message template # - determine the most recent kernel release and add it to the above # - perform documentation update for pmem.io/ndctl cleanup() { rm -rf release mkdir release/ } err() { echo "$1" exit 1 } parse_args() { local args="$*" grep -q "fix" <<< "$args" && rel_fix="1" || rel_fix="" grep -q "ignore_rev" <<< "$args" && ignore_rev="1" || ignore_rev="" } check_branch() { local cur=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) if [ -n "$rel_fix" ]; then # fixup release, expect ndctl-xx.y branch if ! grep -Eq "^ndctl.[0-9]+\.y$" <<< "$cur"; then err "expected an ndctl-xx.y branch for fixup release" fi else # major release, expect main branch if ! grep -Eq "^main$" <<< "$cur"; then err "expected main branch for a major release" fi fi if ! git diff-index --quiet HEAD --; then err "$cur has uncommitted/unstaged changes" fi } last_maj() { git tag | sort -V | grep -E "v[0-9]+$" | tail -1 } last_fix() { local base="$1" git tag | sort -V | grep -E "$base\.?[0-9]*$" | tail -1 } next_maj() { local last="$1" local num=${last#v} newnum="$((num + 1))" echo "v$newnum" } next_fix() { local last="$1" local num=${last##*.} local base=${last%%.*} newnum=$((num + 1)) echo "$base.$newnum" } gen_lists() { local range="$1" git shortlog --no-merges "$range" > release/shortlog git log --no-merges --pretty=format:"%s" "$range" > release/commits c_count=$(git log --pretty=format:"%s" "$range" | wc -l) } # Check libtool versions in meson.build # $1: lib name (currently libndctl, libdaxctl, or libcxl) check_libtool_vers() { local lib="$1" local lib_u="${lib^^}" local libdir="${lib##lib}/lib/" local symfile="${libdir}/${lib}.sym" local last_cur=$(git show $last_ref:meson.build | grep -E "^${lib_u}_CURRENT" | cut -d'=' -f2) local last_rev=$(git show $last_ref:meson.build | grep -E "^${lib_u}_REVISION" | cut -d'=' -f2) local last_age=$(git show $last_ref:meson.build | grep -E "^${lib_u}_AGE" | cut -d'=' -f2) local last_soname=$((last_cur - last_age)) local next_cur=$(git show HEAD:meson.build | grep -E "^${lib_u}_CURRENT" | cut -d'=' -f2) local next_rev=$(git show HEAD:meson.build | grep -E "^${lib_u}_REVISION" | cut -d'=' -f2) local next_age=$(git show HEAD:meson.build | grep -E "^${lib_u}_AGE" | cut -d'=' -f2) local next_soname=$((next_cur - next_age)) local soname_diff=$((next_soname - last_soname)) # generally libtool versions either reset to zero or increase only by one # _CURRENT monotonically increases (by one) if [ "$((next_cur - last_cur))" -gt 1 ]; then err "${lib_u}_CURRENT can increase at most by 1" fi if [ "$next_rev" -ne 0 ]; then if [ "$((next_rev - last_rev))" -gt 1 ]; then err "${lib_u}_REVISION can increase at most by 1" fi fi if [ "$next_age" -ne 0 ]; then if [ "$((next_age - last_age))" -gt 1 ]; then err "${lib_u}_AGE can increase at most by 1" fi fi # test for soname change if [ "$soname_diff" -ne 0 ]; then err "${lib}: expected soname to stay unchanged" fi # tests based on whether symfile changed # compatibility breaking changes are left for libabigail to detect test -s "$symfile" || err "$symfile: not found" if [ -n "$(git diff --name-only $last_ref..HEAD $symfile)" ]; then # symfile has changed, cur and age should increase if [ "$((next_cur - last_cur))" -ne 1 ]; then err "based on $symfile, ${lib_u}_CURRENT should've increased by 1" fi if [ "$((next_age - last_age))" -ne 1 ]; then err "based on $symfile, ${lib_u}_AGE should've increased by 1" fi else # no changes to symfile, revision should've increased if source changed if [ -n "$ignore_rev" ]; then : # skip elif [ -n "$(git diff --name-only $last_ref..HEAD $libdir/)" ]; then if [ "$((next_rev - last_rev))" -ne 1 ]; then err "based on $symfile, ${lib_u}_REVISION should've increased by 1" fi fi fi } # main cleanup parse_args "$*" check_branch [ -e "COPYING" ] || err "Run from the top level of an ndctl tree" last_maj=$(last_maj) test -n "$last_maj" || err "Unable to determine last release" last_fix=$(last_fix $last_maj) test -n "$last_fix" || err "Unable to determine last fixup tag for $last_maj" next_maj=$(next_maj "$last_maj") next_fix=$(next_fix "$last_fix") [ -n "$rel_fix" ] && last_ref="$last_fix" || last_ref="$last_maj" [ -n "$rel_fix" ] && next_ref="$next_fix" || next_ref="$next_maj" check_libtool_vers "libndctl" check_libtool_vers "libdaxctl" check_libtool_vers "libcxl" # HEAD~1 because HEAD would be the release commit gen_lists ${last_ref}..HEAD~1 # For ABI diff purposes, use the latest fixes tag scripts/do_abidiff ${last_fix}..HEAD # once everything passes, update the git-version sed -i -e "s/DEF_VER=[0-9]\+.*/DEF_VER=${next_ref#v}/" git-version echo "Ready to release ndctl-$next_ref with $c_count new commits." echo "Add git-version to the top commit to get the updated version." echo "Use release/commits and release/shortlog to compose the release message" echo "The release commit typically contains the meson.build libtool version" echo "update, and the git-version update." echo "Finally, ensure the release commit as well as the tag are PGP signed." ndctl-81/sles/000077500000000000000000000000001476737544500134045ustar00rootroot00000000000000ndctl-81/sles/header000066400000000000000000000013721476737544500145620ustar00rootroot00000000000000# # spec file for package ndctl # # Copyright (C) 2015 SUSE LINUX GmbH, Nuernberg, Germany. # Copyright (C) 2015-2020 Intel Corporation # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed # upon. The license for this file, and modifications and additions to the # file, is the same license as for the pristine package itself (unless the # license for the pristine package is not an Open Source License, in which # case the license is the MIT License). An "Open Source License" is a # license that conforms to the Open Source Definition (Version 1.9) # published by the Open Source Initiative. # Please submit bugfixes or comments via http://bugs.opensuse.org/ # ndctl-81/sles/meson.build000066400000000000000000000017041476737544500155500ustar00rootroot00000000000000sles_spec1 = vcs_tag( input : '../ndctl.spec.in', output : 'ndctl.spec.sles.in', command: vcs_tagger, replace_string : 'VERSION', ) header = files('header') sles_spec2 = custom_target('ndctl.spec.in', command : [ 'cat', header, '@INPUT@', ], input : sles_spec1, output : 'ndctl.spec.in', capture : true, ) sles_spec3 = custom_target('ndctl.spec', command : [ 'sed', '-e', 's,DAX_DNAME,libdaxctl-devel,g', '-e', 's,CXL_DNAME,libcxl-devel,g', '-e', 's,DNAME,libndctl-devel,g', '-e', 's,%license,%doc,g', '-e', 's,\(^License:.*GPL\)v2,\1-2.0,g', '-e', 's,DAX_LNAME,libdaxctl@0@,g'.format(LIBDAXCTL_CURRENT - LIBDAXCTL_AGE), '-e', 's,CXL_LNAME,libcxl@0@,g'.format(LIBCXL_CURRENT - LIBCXL_AGE), '-e', 's,LNAME,libndctl@0@,g'.format(LIBNDCTL_CURRENT - LIBNDCTL_AGE), '@INPUT@' ], input : sles_spec2, output : 'ndctl.spec', capture : true, ) ndctl-81/test.h000066400000000000000000000036171476737544500135750ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (C) 2015-2020 Intel Corporation. All rights reserved. */ #ifndef __TEST_H__ #define __TEST_H__ #include struct ndctl_test; struct ndctl_ctx; struct ndctl_test *ndctl_test_new(unsigned int kver); int ndctl_test_result(struct ndctl_test *test, int rc); int ndctl_test_get_skipped(struct ndctl_test *test); int ndctl_test_get_attempted(struct ndctl_test *test); int __ndctl_test_attempt(struct ndctl_test *test, unsigned int kver, const char *caller, int line); #define ndctl_test_attempt(t, v) __ndctl_test_attempt(t, v, __func__, __LINE__) void __ndctl_test_skip(struct ndctl_test *test, const char *caller, int line); #define ndctl_test_skip(t) __ndctl_test_skip(t, __func__, __LINE__) struct ndctl_namespace *ndctl_get_test_dev(struct ndctl_ctx *ctx); void builtin_xaction_namespace_reset(void); struct kmod_ctx; struct kmod_module; int ndctl_test_init(struct kmod_ctx **ctx, struct kmod_module **mod, struct ndctl_ctx *nd_ctx, int log_level, struct ndctl_test *test); struct ndctl_ctx; int test_parent_uuid(int loglevel, struct ndctl_test *test, struct ndctl_ctx *ctx); int test_dax_directio(int dax_fd, unsigned long align, void *dax_addr, off_t offset); int test_dax_remap(struct ndctl_test *test, int dax_fd, unsigned long align, void *dax_addr, off_t offset, bool fsdax); #ifdef ENABLE_POISON int test_dax_poison(struct ndctl_test *test, int dax_fd, unsigned long align, void *dax_addr, off_t offset, bool fsdax); #else static inline int test_dax_poison(struct ndctl_test *test, int dax_fd, unsigned long align, void *dax_addr, off_t offset, bool fsdax) { return 0; } #endif int test_dsm_fail(int loglevel, struct ndctl_test *test, struct ndctl_ctx *ctx); int test_libndctl(int loglevel, struct ndctl_test *test, struct ndctl_ctx *ctx); int test_pmem_namespaces(int loglevel, struct ndctl_test *test, struct ndctl_ctx *ctx); #endif /* __TEST_H__ */ ndctl-81/test/000077500000000000000000000000001476737544500134155ustar00rootroot00000000000000ndctl-81/test/ack-shutdown-count-set.c000066400000000000000000000060021476737544500201050ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2018-2020 Intel Corporation. All rights reserved. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int test_dimm(struct ndctl_dimm *dimm) { struct ndctl_cmd *cmd; int rc = 0; cmd = ndctl_dimm_cmd_new_ack_shutdown_count(dimm); if (!cmd) return -ENOMEM; rc = ndctl_cmd_submit_xlat(cmd); if (rc < 0) { fprintf(stderr, "dimm %s LSS enable set failed\n", ndctl_dimm_get_devname(dimm)); goto out; } printf("DIMM %s LSS enable set\n", ndctl_dimm_get_devname(dimm)); out: ndctl_cmd_unref(cmd); return rc; } static void reset_bus(struct ndctl_bus *bus) { struct ndctl_region *region; struct ndctl_dimm *dimm; /* disable all regions so that set_config_data commands are permitted */ ndctl_region_foreach(bus, region) ndctl_region_disable_invalidate(region); ndctl_dimm_foreach(bus, dimm) ndctl_dimm_zero_labels(dimm); } static int do_test(struct ndctl_ctx *ctx, struct ndctl_test *test) { struct ndctl_bus *bus = ndctl_bus_get_by_provider(ctx, "nfit_test.0"); struct ndctl_dimm *dimm; struct ndctl_region *region; struct log_ctx log_ctx; int rc = 0; if (!ndctl_test_attempt(test, KERNEL_VERSION(4, 15, 0))) return 77; if (!bus) return -ENXIO; log_init(&log_ctx, "test/ack-shutdown-count-set", "NDCTL_TEST"); ndctl_bus_wait_probe(bus); ndctl_region_foreach(bus, region) ndctl_region_disable_invalidate(region); ndctl_dimm_foreach(bus, dimm) { fprintf(stderr, "Testing dimm: %s\n", ndctl_dimm_get_devname(dimm)); rc = test_dimm(dimm); if (rc < 0) { fprintf(stderr, "dimm %s failed\n", ndctl_dimm_get_devname(dimm)); goto out; } } out: reset_bus(bus); return rc; } static int test_ack_shutdown_count_set(int loglevel, struct ndctl_test *test, struct ndctl_ctx *ctx) { struct kmod_module *mod; struct kmod_ctx *kmod_ctx; int result = EXIT_FAILURE, err; ndctl_set_log_priority(ctx, loglevel); err = ndctl_test_init(&kmod_ctx, &mod, NULL, loglevel, test); if (err < 0) { result = 77; ndctl_test_skip(test); fprintf(stderr, "%s unavailable skipping tests\n", "nfit_test"); return result; } result = do_test(ctx, test); kmod_module_remove_module(mod, 0); kmod_unref(kmod_ctx); return result; } int main(int argc, char *argv[]) { char *test_env = getenv("NDCTL_TEST_FAMILY"); struct ndctl_test *test = ndctl_test_new(0); struct ndctl_ctx *ctx; int rc; if (!test) { fprintf(stderr, "failed to initialize test\n"); return EXIT_FAILURE; } if (test_env && strcmp(test_env, "PAPR") == 0) return ndctl_test_result(test, 77); rc = ndctl_new(&ctx); if (rc) return ndctl_test_result(test, rc); rc = test_ack_shutdown_count_set(LOG_DEBUG, test, ctx); ndctl_unref(ctx); return ndctl_test_result(test, rc); } ndctl-81/test/align.sh000077500000000000000000000064071476737544500150550ustar00rootroot00000000000000#!/bin/bash -x # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2015-2020 Intel Corporation. All rights reserved. . $(dirname $0)/common rc=77 cleanup() { echo "align.sh: failed at line $1" if [ "x$region" != "x" -a x$save_align != "x" ]; then echo $save_align > $region_path/align fi if [ "x$ns1" != "x" ]; then $NDCTL destroy-namespace -f $ns1 fi if [ "x$ns2" != "x" ]; then $NDCTL destroy-namespace -f $ns2 fi exit $rc } is_aligned() { val=$1 align=$2 if [ $((val & (align - 1))) -eq 0 ]; then return 0 fi return 1 } set -e trap 'err $LINENO cleanup' ERR find_region() { $NDCTL list -R -b ACPI.NFIT | jq -r '[.[] | select(.available_size == .size)][0] | .dev' } region=$(find_region) if [ "x$region" = "xnull" ]; then # this is destructive $NDCTL disable-region -b ACPI.NFIT all $NDCTL init-labels -f -b ACPI.NFIT all $NDCTL enable-region -b ACPI.NFIT all fi region=$(find_region) if [ "x$region" = "xnull" ]; then unset $region echo "unable to find empty region" false fi region_path="/sys/bus/nd/devices/$region" save_align=$(cat $region_path/align) # check that the region is 1G aligned resource=$(cat $region_path/resource) is_aligned $resource $((1 << 30)) || (echo "need a 1GB aligned namespace to test alignment conditions" && false) rc=1 # check that start-aligned, but end-misaligned namespaces can be created # and probed echo 4096 > $region_path/align SIZE=$(((1<<30) + (8<<10))) json=$($NDCTL create-namespace -r $region -s $SIZE -m fsdax -a 4K) eval $(json2var <<< "$json") $NDCTL disable-namespace $dev $NDCTL enable-namespace $dev ns1=$dev # check that start-misaligned namespaces can't be created until the # region alignment is set to a compatible value. # Note the namespace capacity alignment requirement in this case is # SUBSECTION_SIZE (2M) as the data alignment can be satisfied with # metadata padding. json=$($NDCTL create-namespace -r $region -s $SIZE -m fsdax -a 4K -f) || status="failed" if [ $status != "failed" ]; then echo "expected namespace creation failure" eval $(json2var <<< "$json") $NDCTL destroy-namespace -f $dev false fi # check that start-misaligned namespaces can't be probed. Since the # kernel does not support creating this misalignment, force it with a # custom info-block json=$($NDCTL create-namespace -r $region -s $SIZE -m raw) eval $(json2var <<< "$json") $NDCTL disable-namespace $dev $NDCTL write-infoblock $dev -a 4K $NDCTL enable-namespace $dev || status="failed" if [ $status != "failed" ]; then echo "expected namespace enable failure" $NDCTL destroy-namespace -f $dev false fi ns2=$dev # check that namespace with proper inner padding can be enabled, even # though non-zero start_pad namespaces don't support dax $NDCTL write-infoblock $ns2 -a 4K -O 8K $NDCTL enable-namespace $ns2 $NDCTL destroy-namespace $ns2 -f unset ns2 # check that all namespace alignments can be created with the region # alignment at a compatible value SIZE=$((2 << 30)) echo $((16 << 20)) > $region_path/align for i in $((1 << 30)) $((2 << 20)) $((4 << 10)) do json=$($NDCTL create-namespace -r $region -s $SIZE -m fsdax -a $i) eval $(json2var <<< "$json") ns2=$dev $NDCTL disable-namespace $dev $NDCTL enable-namespace $dev $NDCTL destroy-namespace $dev -f unset ns2 done # final cleanup $NDCTL destroy-namespace $ns1 -f exit 0 ndctl-81/test/btt-check.sh000077500000000000000000000071161476737544500156250ustar00rootroot00000000000000#!/bin/bash -E # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2015-2020 Intel Corporation. All rights reserved. dev="" mode="" size="" sector_size="" blockdev="" bs=4096 rc=77 . $(dirname $0)/common trap 'err $LINENO' ERR # sample json: # { # "dev":"namespace5.0", # "mode":"sector", # "size":32440320, # "uuid":"51805176-e124-4635-ae17-0e6a4a16671a", # "sector_size":4096, # "blockdev":"pmem5s" # } check_min_kver "4.14" || do_skip "may not support badblocks clearing on pmem via btt" create() { json=$($NDCTL create-namespace -b $NFIT_TEST_BUS0 -t pmem -m sector) rc=2 eval "$(echo "$json" | json2var)" [ -n "$dev" ] || err "$LINENO" [ "$mode" = "sector" ] || err "$LINENO" [ -n "$size" ] || err "$LINENO" [ -n "$sector_size" ] || err "$LINENO" [ -n "$blockdev" ] || err "$LINENO" [ $size -gt 0 ] || err "$LINENO" } # re-enable the BTT namespace, and do IO to it in an attempt to # verify it still comes up ok, and functions as expected post_repair_test() { echo "${FUNCNAME[0]}: I/O to BTT namespace" test -b /dev/$blockdev dd if=/dev/urandom of=test-bin bs=$sector_size count=$((size/sector_size)) > /dev/null 2>&1 dd if=test-bin of=/dev/$blockdev bs=$sector_size count=$((size/sector_size)) > /dev/null 2>&1 dd if=/dev/$blockdev of=test-bin-read bs=$sector_size count=$((size/sector_size)) > /dev/null 2>&1 diff test-bin test-bin-read rm -f test-bin* echo "done" } test_normal() { echo "=== ${FUNCNAME[0]} ===" # disable the namespace $NDCTL disable-namespace $dev $NDCTL check-namespace $dev $NDCTL enable-namespace $dev post_repair_test } test_force() { echo "=== ${FUNCNAME[0]} ===" $NDCTL check-namespace --force $dev post_repair_test } set_raw() { $NDCTL disable-namespace $dev echo -n "set raw_mode: " echo 1 | tee /sys/bus/nd/devices/$dev/force_raw $NDCTL enable-namespace $dev raw_bdev="${blockdev%%s}" test -b /dev/$raw_bdev raw_size="$(cat /sys/bus/nd/devices/$dev/size)" } unset_raw() { $NDCTL disable-namespace $dev echo -n "set raw_mode: " echo 0 | tee /sys/bus/nd/devices/$dev/force_raw $NDCTL enable-namespace $dev raw_bdev="" } test_bad_info2() { echo "=== ${FUNCNAME[0]} ===" set_raw seek="$((raw_size/bs - 1))" echo "wiping info2 block (offset = $seek blocks)" dd if=/dev/zero of=/dev/$raw_bdev bs=$bs count=1 seek=$seek unset_raw $NDCTL disable-namespace $dev $NDCTL check-namespace $dev 2>&1 | grep "info2 needs to be restored" $NDCTL check-namespace --repair $dev $NDCTL enable-namespace $dev post_repair_test } test_bad_info() { echo "=== ${FUNCNAME[0]} ===" set_raw echo "wiping info block" dd if=/dev/zero of=/dev/$raw_bdev bs=$bs count=2 seek=0 unset_raw $NDCTL disable-namespace $dev $NDCTL check-namespace $dev 2>&1 | grep -E "info block at offset .* needs to be restored" $NDCTL check-namespace --repair $dev $NDCTL enable-namespace $dev post_repair_test } test_bitmap() { echo "=== ${FUNCNAME[0]} ===" reset && create set_raw # scribble over the last 4K of the map rm -f /tmp/scribble for (( i=0 ; i<512 ; i++ )); do echo -n -e \\x1e\\x1e\\x00\\xc0\\x1e\\x1e\\x00\\xc0 >> /tmp/scribble done seek="$((raw_size/bs - (256*64/bs) - 2))" echo "scribbling over map entries (offset = $seek blocks)" dd if=/tmp/scribble of=/dev/$raw_bdev bs=$bs seek=$seek rm -f /tmp/scribble unset_raw $NDCTL disable-namespace $dev $NDCTL check-namespace $dev 2>&1 | grep "bitmap error" # This is not repairable reset && create } do_tests() { test_normal test_force test_bad_info2 test_bad_info test_bitmap } # setup (reset nfit_test dimms, create the BTT namespace) modprobe nfit_test rc=1 reset && create do_tests reset _cleanup exit 0 ndctl-81/test/btt-errors.sh000077500000000000000000000076241476737544500160700ustar00rootroot00000000000000#!/bin/bash -x # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2015-2020 Intel Corporation. All rights reserved. MNT=test_btt_mnt FILE=image blockdev="" rc=77 . $(dirname $0)/common cleanup() { if grep -q "$MNT" /proc/mounts; then umount $MNT else rc=77 fi rm -rf $MNT } force_raw() { raw="$1" if grep -q "$MNT" /proc/mounts; then umount $MNT; fi $NDCTL disable-namespace "$dev" echo "$raw" > "/sys/bus/nd/devices/$dev/force_raw" $NDCTL enable-namespace "$dev" echo "Set $dev to raw mode: $raw" if [[ "$raw" == "1" ]]; then raw_bdev=${blockdev%s} test -b "/dev/$raw_bdev" else raw_bdev="" fi } check_min_kver "4.15" || do_skip "may lack BTT error handling" set -e mkdir -p $MNT trap 'err $LINENO cleanup' ERR # setup (reset nfit_test dimms) modprobe nfit_test resetV rc=1 # create a btt namespace and clear errors (if any) dev="x" json=$($NDCTL create-namespace -b $NFIT_TEST_BUS0 -t pmem -m sector) eval "$(echo "$json" | json2var)" [ $dev = "x" ] && echo "fail: $LINENO" && exit 1 force_raw 1 if read -r sector len < "/sys/block/$raw_bdev/badblocks"; then dd of=/dev/$raw_bdev if=/dev/zero oflag=direct bs=512 seek="$sector" count="$len" fi force_raw 0 mkfs.ext4 "/dev/$blockdev" -b 4096 mount -o nodelalloc "/dev/$blockdev" $MNT # prepare an image file with random data dd if=/dev/urandom of=$FILE bs=4096 count=1 test -s $FILE # copy it to the file system cp $FILE $MNT/$FILE # Get the start sector for the file start_sect=$(filefrag -v -b512 $MNT/$FILE | grep -E "^[ ]+[0-9]+.*" | head -1 | awk '{ print $4 }' | cut -d. -f1) start_4k=$((start_sect/8)) test -n "$start_sect" echo "start sector of the file is: $start_sect (512B) or $start_4k (4096B)" # figure out the btt offset force_raw 1 # calculate start of the map map=$(hexdump -s 96 -n 4 "/dev/$raw_bdev" | head -1 | cut -d' ' -f2-) map=$(tr -d ' ' <<< "0x${map#* }${map%% *}") printf "btt map starts at: %x\n" "$map" # calculate map entry byte offset for the file's block map_idx=$((map + (4 * start_4k))) printf "btt map entry location for sector %x: %x\n" "$start_4k" "$map_idx" # read the map entry map_ent=$(hexdump -s $map_idx -n 4 "/dev/$raw_bdev" | head -1 | cut -d' ' -f2-) map_ent=$(tr -d ' ' <<< "0x${map_ent#* }${map_ent%% *}") map_ent=$((map_ent & 0x3fffffff)) printf "btt map entry: 0x%x\n" "$map_ent" # calculate the data offset dataoff=$(((map_ent * 4096) + 4096)) printf "dataoff: 0x%x\n" "$dataoff" bb_inj=$((dataoff/512)) # inject badblocks for one page at the start of the file $NDCTL inject-error --block="$bb_inj" --count=8 $dev $NDCTL start-scrub $NFIT_TEST_BUS0 && $NDCTL wait-scrub $NFIT_TEST_BUS0 force_raw 0 mount -o nodelalloc "/dev/$blockdev" $MNT # make sure reading the first block of the file fails as expected : The following 'dd' is expected to hit an I/O Error dd if=$MNT/$FILE of=/dev/null iflag=direct bs=4096 count=1 && err $LINENO || true # write via btt to clear the error dd if=/dev/zero of=$MNT/$FILE oflag=direct bs=4096 count=1 # read again and that should succeed dd if=$MNT/$FILE of=/dev/null iflag=direct bs=4096 count=1 ## ensure we get an EIO for errors in namespace metadata # reset everything to get a clean log if grep -q "$MNT" /proc/mounts; then umount $MNT; fi resetV dev="x" json=$($NDCTL create-namespace -b $NFIT_TEST_BUS0 -t pmem -m sector) eval "$(echo "$json" | json2var)" [ $dev = "x" ] && echo "fail: $LINENO" && exit 1 # insert error at an arbitrary offset in the map (sector 0) force_raw 1 map=$(hexdump -s 96 -n 4 "/dev/$raw_bdev" | head -1 | cut -d' ' -f2-) map=$(tr -d ' ' <<< "0x${map#* }${map%% *}") bb_inj=$((map/512)) $NDCTL inject-error --block="$bb_inj" --count=1 $dev $NDCTL start-scrub $NFIT_TEST_BUS0 && $NDCTL wait-scrub $NFIT_TEST_BUS0 force_raw 0 # make sure reading the first block of the namespace fails : The following 'dd' is expected to hit an I/O Error dd if=/dev/$blockdev of=/dev/null iflag=direct bs=4096 count=1 && err $LINENO || true # done, exit reset cleanup _cleanup exit 0 ndctl-81/test/btt-pad-compat.sh000077500000000000000000000102211476737544500165640ustar00rootroot00000000000000#!/bin/bash -Ex # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2015-2020 Intel Corporation. All rights reserved. dev="" size="" blockdev="" rc=77 BASE=$(dirname $0) . $BASE/common trap 'err $LINENO' ERR # sample json: #{ # "dev":"namespace7.0", # "mode":"fsdax", # "size":"60.00 MiB (62.92 MB)", # "uuid":"f1baa71a-d165-4da4-bb6a-083a2b0e6469", # "blockdev":"pmem7", #} create() { json=$($NDCTL create-namespace -b $NFIT_TEST_BUS0 -t pmem -m sector) rc=2 eval "$(echo "$json" | json2var)" [ -n "$dev" ] || err "$LINENO" [ -n "$size" ] || err "$LINENO" [ -n "$blockdev" ] || err "$LINENO" [ $size -gt 0 ] || err "$LINENO" bttdev=$(cat /sys/bus/nd/devices/$dev/holder) [ -n "$bttdev" ] || err "$LINENO" if [ ! -e /sys/kernel/debug/btt/$bttdev/arena0/log_index_0 ]; then do_skip "seems to be missing the BTT compatibility fixes, skipping." fi } verify_idx() { idx0="$1" idx1="$2" # check debugfs is mounted if ! grep -qE "debugfs" /proc/mounts; then mount -t debugfs none /sys/kernel/debug fi test $(cat /sys/kernel/debug/btt/$bttdev/arena0/log_index_0) -eq "$idx0" test $(cat /sys/kernel/debug/btt/$bttdev/arena0/log_index_1) -eq "$idx1" } do_random_io() { local bdev="$1" dd if=/dev/urandom of="$bdev" bs=4096 count=32 seek=0 & dd if=/dev/urandom of="$bdev" bs=4096 count=32 seek=32 & dd if=/dev/urandom of="$bdev" bs=4096 count=32 seek=64 & dd if=/dev/urandom of="$bdev" bs=4096 count=32 seek=128 & dd if=/dev/urandom of="$bdev" bs=4096 count=32 seek=256 & dd if=/dev/urandom of="$bdev" bs=4096 count=32 seek=512 & dd if=/dev/urandom of="$bdev" bs=4096 count=32 seek=1024 & dd if=/dev/urandom of="$bdev" bs=4096 count=32 seek=2048 & wait } cycle_ns() { local ns="$1" $NDCTL disable-namespace $ns $NDCTL enable-namespace $ns } force_raw() { raw="$1" $NDCTL disable-namespace "$dev" echo "$raw" > "/sys/bus/nd/devices/$dev/force_raw" $NDCTL enable-namespace "$dev" echo "Set $dev to raw mode: $raw" if [[ "$raw" == "1" ]]; then raw_bdev=${blockdev%s} test -b "/dev/$raw_bdev" else raw_bdev="" fi } copy_xxd_img() { local bdev="$1" local xxd_patch="$BASE/btt-pad-compat.xxd" test -s "$xxd_patch" test -b "$bdev" xxd -r "$xxd_patch" "$bdev" } create_oldfmt_ns() { # create null-uuid namespace, note that this requires a kernel # that supports a raw namespace with a 4K sector size, prior to # v4.13 raw namespaces are limited to 512-byte sector size. rc=77 json=$($NDCTL create-namespace -b $NFIT_TEST_BUS0 -s 64M -t pmem -m raw -l 4096 -u 00000000-0000-0000-0000-000000000000) rc=2 eval "$(echo "$json" | json2var)" [ -n "$dev" ] || err "$LINENO" [ -n "$size" ] || err "$LINENO" [ $size -gt 0 ] || err "$LINENO" # reconfig it to sector mode json=$($NDCTL create-namespace -b $NFIT_TEST_BUS0 -e $dev -m sector --force) eval "$(echo "$json" | json2var)" [ -n "$dev" ] || err "$LINENO" [ -n "$size" ] || err "$LINENO" [ -n "$blockdev" ] || err "$LINENO" [ $size -gt 0 ] || err "$LINENO" bttdev=$(cat /sys/bus/nd/devices/$dev/holder) [ -n "$bttdev" ] || err "$LINENO" rc=1 # copy old-padding-format btt image, and try to re-enable the resulting btt force_raw 1 copy_xxd_img "/dev/$raw_bdev" force_raw 0 test -b "/dev/$blockdev" } ns_info_wipe() { force_raw 1 dd if=/dev/zero of=/dev/$raw_bdev bs=4096 count=2 } do_tests() { # regular btt create verify_idx 0 1 # do io, and cycle namespace, verify indices do_random_io "/dev/$blockdev" cycle_ns "$dev" verify_idx 0 1 # do the same with an old format namespace resetV create_oldfmt_ns verify_idx 0 2 # do io, and cycle namespace, verify indices do_random_io "/dev/$blockdev" cycle_ns "$dev" verify_idx 0 2 # rewrite log using ndctl, verify conversion to new format $NDCTL check-namespace --rewrite-log --repair --force --verbose $dev do_random_io "/dev/$blockdev" cycle_ns "$dev" verify_idx 0 1 # check-namespace again to make sure everything is ok $NDCTL check-namespace --force --verbose $dev # the old format btt metadata was created with a null parent uuid, # making it 'stickier' than a normally created btt. Be sure to clean # it up by wiping the info block ns_info_wipe } modprobe nfit_test check_prereq xxd rc=1 reset do_tests reset _cleanup exit 0 ndctl-81/test/btt-pad-compat.xxd000066400000000000000000001074311476737544500167640ustar00rootroot000000000000000000000: 4254 545f 4152 454e 415f 494e 464f 0000 BTT_ARENA_INFO.. 0000010: f0d6 4d51 7ad4 44e2 8757 6b8e 8e4f 7500 ..MQz.D..Wk..Ou. 0000020: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 0000030: 0000 0000 0200 0000 0010 0000 e93e 0000 .............>.. 0000040: 0010 0000 e93f 0000 0001 0000 0010 0000 .....?.......... 0000050: 0000 0000 0000 0000 0010 0000 0000 0000 ................ 0000060: 00b0 fe03 0000 0000 00b0 ff03 0000 0000 ................ 0000070: 00f0 ff03 0000 0000 0000 0000 0000 0000 ................ 0000080: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 0000ff0: 0000 0000 0000 0000 5db7 55ca 03a9 d829 ........].U....) 0001000: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ef0000: cbe4 21e6 d01b d67b c5f7 7bf5 d943 abff ..!....{..{..C.. 3ef0010: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3feb000: ef3e 00c0 0000 0000 0000 0000 0000 0000 .>.............. 3feb010: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffb000: 0000 0000 e93e 0000 e93e 0000 0100 0000 .....>...>...... 3ffb010: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffb040: 0100 0000 ea3e 0000 ea3e 0000 0100 0000 .....>...>...... 3ffb050: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffb080: 0200 0000 eb3e 0000 eb3e 0000 0100 0000 .....>...>...... 3ffb090: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffb0c0: 0300 0000 ec3e 0000 ec3e 0000 0100 0000 .....>...>...... 3ffb0d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffb100: 0400 0000 ed3e 0000 ed3e 0000 0100 0000 .....>...>...... 3ffb110: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffb140: 0500 0000 ee3e 0000 ee3e 0000 0100 0000 .....>...>...... 3ffb150: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffb180: 0600 0000 ef3e 0000 ef3e 0000 0100 0000 .....>...>...... 3ffb190: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 3ffb1a0: 0000 0000 0000 0000 ef3e 0000 0200 0000 .........>...... 3ffb1b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 3ffb1c0: 0700 0000 f03e 0000 f03e 0000 0100 0000 .....>...>...... 3ffb1d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffb200: 0800 0000 f13e 0000 f13e 0000 0100 0000 .....>...>...... 3ffb210: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffb240: 0900 0000 f23e 0000 f23e 0000 0100 0000 .....>...>...... 3ffb250: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffb280: 0a00 0000 f33e 0000 f33e 0000 0100 0000 .....>...>...... 3ffb290: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffb2c0: 0b00 0000 f43e 0000 f43e 0000 0100 0000 .....>...>...... 3ffb2d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffb300: 0c00 0000 f53e 0000 f53e 0000 0100 0000 .....>...>...... 3ffb310: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffb340: 0d00 0000 f63e 0000 f63e 0000 0100 0000 .....>...>...... 3ffb350: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffb380: 0e00 0000 f73e 0000 f73e 0000 0100 0000 .....>...>...... 3ffb390: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffb3c0: 0f00 0000 f83e 0000 f83e 0000 0100 0000 .....>...>...... 3ffb3d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffb400: 1000 0000 f93e 0000 f93e 0000 0100 0000 .....>...>...... 3ffb410: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffb440: 1100 0000 fa3e 0000 fa3e 0000 0100 0000 .....>...>...... 3ffb450: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffb480: 1200 0000 fb3e 0000 fb3e 0000 0100 0000 .....>...>...... 3ffb490: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffb4c0: 1300 0000 fc3e 0000 fc3e 0000 0100 0000 .....>...>...... 3ffb4d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffb500: 1400 0000 fd3e 0000 fd3e 0000 0100 0000 .....>...>...... 3ffb510: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffb540: 1500 0000 fe3e 0000 fe3e 0000 0100 0000 .....>...>...... 3ffb550: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffb580: 1600 0000 ff3e 0000 ff3e 0000 0100 0000 .....>...>...... 3ffb590: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffb5c0: 1700 0000 003f 0000 003f 0000 0100 0000 .....?...?...... 3ffb5d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffb600: 1800 0000 013f 0000 013f 0000 0100 0000 .....?...?...... 3ffb610: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffb640: 1900 0000 023f 0000 023f 0000 0100 0000 .....?...?...... 3ffb650: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffb680: 1a00 0000 033f 0000 033f 0000 0100 0000 .....?...?...... 3ffb690: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffb6c0: 1b00 0000 043f 0000 043f 0000 0100 0000 .....?...?...... 3ffb6d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffb700: 1c00 0000 053f 0000 053f 0000 0100 0000 .....?...?...... 3ffb710: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffb740: 1d00 0000 063f 0000 063f 0000 0100 0000 .....?...?...... 3ffb750: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffb780: 1e00 0000 073f 0000 073f 0000 0100 0000 .....?...?...... 3ffb790: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffb7c0: 1f00 0000 083f 0000 083f 0000 0100 0000 .....?...?...... 3ffb7d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffb800: 2000 0000 093f 0000 093f 0000 0100 0000 ....?...?...... 3ffb810: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffb840: 2100 0000 0a3f 0000 0a3f 0000 0100 0000 !....?...?...... 3ffb850: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffb880: 2200 0000 0b3f 0000 0b3f 0000 0100 0000 "....?...?...... 3ffb890: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffb8c0: 2300 0000 0c3f 0000 0c3f 0000 0100 0000 #....?...?...... 3ffb8d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffb900: 2400 0000 0d3f 0000 0d3f 0000 0100 0000 $....?...?...... 3ffb910: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffb940: 2500 0000 0e3f 0000 0e3f 0000 0100 0000 %....?...?...... 3ffb950: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffb980: 2600 0000 0f3f 0000 0f3f 0000 0100 0000 &....?...?...... 3ffb990: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffb9c0: 2700 0000 103f 0000 103f 0000 0100 0000 '....?...?...... 3ffb9d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffba00: 2800 0000 113f 0000 113f 0000 0100 0000 (....?...?...... 3ffba10: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffba40: 2900 0000 123f 0000 123f 0000 0100 0000 )....?...?...... 3ffba50: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffba80: 2a00 0000 133f 0000 133f 0000 0100 0000 *....?...?...... 3ffba90: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffbac0: 2b00 0000 143f 0000 143f 0000 0100 0000 +....?...?...... 3ffbad0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffbb00: 2c00 0000 153f 0000 153f 0000 0100 0000 ,....?...?...... 3ffbb10: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffbb40: 2d00 0000 163f 0000 163f 0000 0100 0000 -....?...?...... 3ffbb50: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffbb80: 2e00 0000 173f 0000 173f 0000 0100 0000 .....?...?...... 3ffbb90: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffbbc0: 2f00 0000 183f 0000 183f 0000 0100 0000 /....?...?...... 3ffbbd0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffbc00: 3000 0000 193f 0000 193f 0000 0100 0000 0....?...?...... 3ffbc10: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffbc40: 3100 0000 1a3f 0000 1a3f 0000 0100 0000 1....?...?...... 3ffbc50: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffbc80: 3200 0000 1b3f 0000 1b3f 0000 0100 0000 2....?...?...... 3ffbc90: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffbcc0: 3300 0000 1c3f 0000 1c3f 0000 0100 0000 3....?...?...... 3ffbcd0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffbd00: 3400 0000 1d3f 0000 1d3f 0000 0100 0000 4....?...?...... 3ffbd10: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffbd40: 3500 0000 1e3f 0000 1e3f 0000 0100 0000 5....?...?...... 3ffbd50: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffbd80: 3600 0000 1f3f 0000 1f3f 0000 0100 0000 6....?...?...... 3ffbd90: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffbdc0: 3700 0000 203f 0000 203f 0000 0100 0000 7... ?.. ?...... 3ffbdd0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffbe00: 3800 0000 213f 0000 213f 0000 0100 0000 8...!?..!?...... 3ffbe10: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffbe40: 3900 0000 223f 0000 223f 0000 0100 0000 9..."?.."?...... 3ffbe50: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffbe80: 3a00 0000 233f 0000 233f 0000 0100 0000 :...#?..#?...... 3ffbe90: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffbec0: 3b00 0000 243f 0000 243f 0000 0100 0000 ;...$?..$?...... 3ffbed0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffbf00: 3c00 0000 253f 0000 253f 0000 0100 0000 <...%?..%?...... 3ffbf10: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffbf40: 3d00 0000 263f 0000 263f 0000 0100 0000 =...&?..&?...... 3ffbf50: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffbf80: 3e00 0000 273f 0000 273f 0000 0100 0000 >...'?..'?...... 3ffbf90: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffbfc0: 3f00 0000 283f 0000 283f 0000 0100 0000 ?...(?..(?...... 3ffbfd0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffc000: 4000 0000 293f 0000 293f 0000 0100 0000 @...)?..)?...... 3ffc010: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffc040: 4100 0000 2a3f 0000 2a3f 0000 0100 0000 A...*?..*?...... 3ffc050: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffc080: 4200 0000 2b3f 0000 2b3f 0000 0100 0000 B...+?..+?...... 3ffc090: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffc0c0: 4300 0000 2c3f 0000 2c3f 0000 0100 0000 C...,?..,?...... 3ffc0d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffc100: 4400 0000 2d3f 0000 2d3f 0000 0100 0000 D...-?..-?...... 3ffc110: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffc140: 4500 0000 2e3f 0000 2e3f 0000 0100 0000 E....?...?...... 3ffc150: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffc180: 4600 0000 2f3f 0000 2f3f 0000 0100 0000 F.../?../?...... 3ffc190: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffc1c0: 4700 0000 303f 0000 303f 0000 0100 0000 G...0?..0?...... 3ffc1d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffc200: 4800 0000 313f 0000 313f 0000 0100 0000 H...1?..1?...... 3ffc210: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffc240: 4900 0000 323f 0000 323f 0000 0100 0000 I...2?..2?...... 3ffc250: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffc280: 4a00 0000 333f 0000 333f 0000 0100 0000 J...3?..3?...... 3ffc290: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffc2c0: 4b00 0000 343f 0000 343f 0000 0100 0000 K...4?..4?...... 3ffc2d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffc300: 4c00 0000 353f 0000 353f 0000 0100 0000 L...5?..5?...... 3ffc310: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffc340: 4d00 0000 363f 0000 363f 0000 0100 0000 M...6?..6?...... 3ffc350: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffc380: 4e00 0000 373f 0000 373f 0000 0100 0000 N...7?..7?...... 3ffc390: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffc3c0: 4f00 0000 383f 0000 383f 0000 0100 0000 O...8?..8?...... 3ffc3d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffc400: 5000 0000 393f 0000 393f 0000 0100 0000 P...9?..9?...... 3ffc410: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffc440: 5100 0000 3a3f 0000 3a3f 0000 0100 0000 Q...:?..:?...... 3ffc450: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffc480: 5200 0000 3b3f 0000 3b3f 0000 0100 0000 R...;?..;?...... 3ffc490: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffc4c0: 5300 0000 3c3f 0000 3c3f 0000 0100 0000 S...?..>?...... 3ffc550: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffc580: 5600 0000 3f3f 0000 3f3f 0000 0100 0000 V...??..??...... 3ffc590: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffc5c0: 5700 0000 403f 0000 403f 0000 0100 0000 W...@?..@?...... 3ffc5d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffc600: 5800 0000 413f 0000 413f 0000 0100 0000 X...A?..A?...... 3ffc610: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffc640: 5900 0000 423f 0000 423f 0000 0100 0000 Y...B?..B?...... 3ffc650: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffc680: 5a00 0000 433f 0000 433f 0000 0100 0000 Z...C?..C?...... 3ffc690: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffc6c0: 5b00 0000 443f 0000 443f 0000 0100 0000 [...D?..D?...... 3ffc6d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffc700: 5c00 0000 453f 0000 453f 0000 0100 0000 \...E?..E?...... 3ffc710: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffc740: 5d00 0000 463f 0000 463f 0000 0100 0000 ]...F?..F?...... 3ffc750: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffc780: 5e00 0000 473f 0000 473f 0000 0100 0000 ^...G?..G?...... 3ffc790: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffc7c0: 5f00 0000 483f 0000 483f 0000 0100 0000 _...H?..H?...... 3ffc7d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffc800: 6000 0000 493f 0000 493f 0000 0100 0000 `...I?..I?...... 3ffc810: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffc840: 6100 0000 4a3f 0000 4a3f 0000 0100 0000 a...J?..J?...... 3ffc850: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffc880: 6200 0000 4b3f 0000 4b3f 0000 0100 0000 b...K?..K?...... 3ffc890: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffc8c0: 6300 0000 4c3f 0000 4c3f 0000 0100 0000 c...L?..L?...... 3ffc8d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffc900: 6400 0000 4d3f 0000 4d3f 0000 0100 0000 d...M?..M?...... 3ffc910: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffc940: 6500 0000 4e3f 0000 4e3f 0000 0100 0000 e...N?..N?...... 3ffc950: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffc980: 6600 0000 4f3f 0000 4f3f 0000 0100 0000 f...O?..O?...... 3ffc990: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffc9c0: 6700 0000 503f 0000 503f 0000 0100 0000 g...P?..P?...... 3ffc9d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffca00: 6800 0000 513f 0000 513f 0000 0100 0000 h...Q?..Q?...... 3ffca10: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffca40: 6900 0000 523f 0000 523f 0000 0100 0000 i...R?..R?...... 3ffca50: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffca80: 6a00 0000 533f 0000 533f 0000 0100 0000 j...S?..S?...... 3ffca90: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffcac0: 6b00 0000 543f 0000 543f 0000 0100 0000 k...T?..T?...... 3ffcad0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffcb00: 6c00 0000 553f 0000 553f 0000 0100 0000 l...U?..U?...... 3ffcb10: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffcb40: 6d00 0000 563f 0000 563f 0000 0100 0000 m...V?..V?...... 3ffcb50: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffcb80: 6e00 0000 573f 0000 573f 0000 0100 0000 n...W?..W?...... 3ffcb90: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffcbc0: 6f00 0000 583f 0000 583f 0000 0100 0000 o...X?..X?...... 3ffcbd0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffcc00: 7000 0000 593f 0000 593f 0000 0100 0000 p...Y?..Y?...... 3ffcc10: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffcc40: 7100 0000 5a3f 0000 5a3f 0000 0100 0000 q...Z?..Z?...... 3ffcc50: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffcc80: 7200 0000 5b3f 0000 5b3f 0000 0100 0000 r...[?..[?...... 3ffcc90: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffccc0: 7300 0000 5c3f 0000 5c3f 0000 0100 0000 s...\?..\?...... 3ffccd0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffcd00: 7400 0000 5d3f 0000 5d3f 0000 0100 0000 t...]?..]?...... 3ffcd10: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffcd40: 7500 0000 5e3f 0000 5e3f 0000 0100 0000 u...^?..^?...... 3ffcd50: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffcd80: 7600 0000 5f3f 0000 5f3f 0000 0100 0000 v..._?.._?...... 3ffcd90: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffcdc0: 7700 0000 603f 0000 603f 0000 0100 0000 w...`?..`?...... 3ffcdd0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffce00: 7800 0000 613f 0000 613f 0000 0100 0000 x...a?..a?...... 3ffce10: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffce40: 7900 0000 623f 0000 623f 0000 0100 0000 y...b?..b?...... 3ffce50: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffce80: 7a00 0000 633f 0000 633f 0000 0100 0000 z...c?..c?...... 3ffce90: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffcec0: 7b00 0000 643f 0000 643f 0000 0100 0000 {...d?..d?...... 3ffced0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffcf00: 7c00 0000 653f 0000 653f 0000 0100 0000 |...e?..e?...... 3ffcf10: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffcf40: 7d00 0000 663f 0000 663f 0000 0100 0000 }...f?..f?...... 3ffcf50: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffcf80: 7e00 0000 673f 0000 673f 0000 0100 0000 ~...g?..g?...... 3ffcf90: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffcfc0: 7f00 0000 683f 0000 683f 0000 0100 0000 ....h?..h?...... 3ffcfd0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffd000: 8000 0000 693f 0000 693f 0000 0100 0000 ....i?..i?...... 3ffd010: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffd040: 8100 0000 6a3f 0000 6a3f 0000 0100 0000 ....j?..j?...... 3ffd050: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffd080: 8200 0000 6b3f 0000 6b3f 0000 0100 0000 ....k?..k?...... 3ffd090: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffd0c0: 8300 0000 6c3f 0000 6c3f 0000 0100 0000 ....l?..l?...... 3ffd0d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffd100: 8400 0000 6d3f 0000 6d3f 0000 0100 0000 ....m?..m?...... 3ffd110: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffd140: 8500 0000 6e3f 0000 6e3f 0000 0100 0000 ....n?..n?...... 3ffd150: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffd180: 8600 0000 6f3f 0000 6f3f 0000 0100 0000 ....o?..o?...... 3ffd190: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffd1c0: 8700 0000 703f 0000 703f 0000 0100 0000 ....p?..p?...... 3ffd1d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffd200: 8800 0000 713f 0000 713f 0000 0100 0000 ....q?..q?...... 3ffd210: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffd240: 8900 0000 723f 0000 723f 0000 0100 0000 ....r?..r?...... 3ffd250: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffd280: 8a00 0000 733f 0000 733f 0000 0100 0000 ....s?..s?...... 3ffd290: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffd2c0: 8b00 0000 743f 0000 743f 0000 0100 0000 ....t?..t?...... 3ffd2d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffd300: 8c00 0000 753f 0000 753f 0000 0100 0000 ....u?..u?...... 3ffd310: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffd340: 8d00 0000 763f 0000 763f 0000 0100 0000 ....v?..v?...... 3ffd350: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffd380: 8e00 0000 773f 0000 773f 0000 0100 0000 ....w?..w?...... 3ffd390: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffd3c0: 8f00 0000 783f 0000 783f 0000 0100 0000 ....x?..x?...... 3ffd3d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffd400: 9000 0000 793f 0000 793f 0000 0100 0000 ....y?..y?...... 3ffd410: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffd440: 9100 0000 7a3f 0000 7a3f 0000 0100 0000 ....z?..z?...... 3ffd450: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffd480: 9200 0000 7b3f 0000 7b3f 0000 0100 0000 ....{?..{?...... 3ffd490: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffd4c0: 9300 0000 7c3f 0000 7c3f 0000 0100 0000 ....|?..|?...... 3ffd4d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffd500: 9400 0000 7d3f 0000 7d3f 0000 0100 0000 ....}?..}?...... 3ffd510: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffd540: 9500 0000 7e3f 0000 7e3f 0000 0100 0000 ....~?..~?...... 3ffd550: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffd580: 9600 0000 7f3f 0000 7f3f 0000 0100 0000 .....?...?...... 3ffd590: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffd5c0: 9700 0000 803f 0000 803f 0000 0100 0000 .....?...?...... 3ffd5d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffd600: 9800 0000 813f 0000 813f 0000 0100 0000 .....?...?...... 3ffd610: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffd640: 9900 0000 823f 0000 823f 0000 0100 0000 .....?...?...... 3ffd650: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffd680: 9a00 0000 833f 0000 833f 0000 0100 0000 .....?...?...... 3ffd690: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffd6c0: 9b00 0000 843f 0000 843f 0000 0100 0000 .....?...?...... 3ffd6d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffd700: 9c00 0000 853f 0000 853f 0000 0100 0000 .....?...?...... 3ffd710: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffd740: 9d00 0000 863f 0000 863f 0000 0100 0000 .....?...?...... 3ffd750: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffd780: 9e00 0000 873f 0000 873f 0000 0100 0000 .....?...?...... 3ffd790: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffd7c0: 9f00 0000 883f 0000 883f 0000 0100 0000 .....?...?...... 3ffd7d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffd800: a000 0000 893f 0000 893f 0000 0100 0000 .....?...?...... 3ffd810: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffd840: a100 0000 8a3f 0000 8a3f 0000 0100 0000 .....?...?...... 3ffd850: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffd880: a200 0000 8b3f 0000 8b3f 0000 0100 0000 .....?...?...... 3ffd890: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffd8c0: a300 0000 8c3f 0000 8c3f 0000 0100 0000 .....?...?...... 3ffd8d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffd900: a400 0000 8d3f 0000 8d3f 0000 0100 0000 .....?...?...... 3ffd910: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffd940: a500 0000 8e3f 0000 8e3f 0000 0100 0000 .....?...?...... 3ffd950: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffd980: a600 0000 8f3f 0000 8f3f 0000 0100 0000 .....?...?...... 3ffd990: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffd9c0: a700 0000 903f 0000 903f 0000 0100 0000 .....?...?...... 3ffd9d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffda00: a800 0000 913f 0000 913f 0000 0100 0000 .....?...?...... 3ffda10: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffda40: a900 0000 923f 0000 923f 0000 0100 0000 .....?...?...... 3ffda50: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffda80: aa00 0000 933f 0000 933f 0000 0100 0000 .....?...?...... 3ffda90: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffdac0: ab00 0000 943f 0000 943f 0000 0100 0000 .....?...?...... 3ffdad0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffdb00: ac00 0000 953f 0000 953f 0000 0100 0000 .....?...?...... 3ffdb10: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffdb40: ad00 0000 963f 0000 963f 0000 0100 0000 .....?...?...... 3ffdb50: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffdb80: ae00 0000 973f 0000 973f 0000 0100 0000 .....?...?...... 3ffdb90: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffdbc0: af00 0000 983f 0000 983f 0000 0100 0000 .....?...?...... 3ffdbd0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffdc00: b000 0000 993f 0000 993f 0000 0100 0000 .....?...?...... 3ffdc10: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffdc40: b100 0000 9a3f 0000 9a3f 0000 0100 0000 .....?...?...... 3ffdc50: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffdc80: b200 0000 9b3f 0000 9b3f 0000 0100 0000 .....?...?...... 3ffdc90: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffdcc0: b300 0000 9c3f 0000 9c3f 0000 0100 0000 .....?...?...... 3ffdcd0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffdd00: b400 0000 9d3f 0000 9d3f 0000 0100 0000 .....?...?...... 3ffdd10: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffdd40: b500 0000 9e3f 0000 9e3f 0000 0100 0000 .....?...?...... 3ffdd50: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffdd80: b600 0000 9f3f 0000 9f3f 0000 0100 0000 .....?...?...... 3ffdd90: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffddc0: b700 0000 a03f 0000 a03f 0000 0100 0000 .....?...?...... 3ffddd0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffde00: b800 0000 a13f 0000 a13f 0000 0100 0000 .....?...?...... 3ffde10: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffde40: b900 0000 a23f 0000 a23f 0000 0100 0000 .....?...?...... 3ffde50: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffde80: ba00 0000 a33f 0000 a33f 0000 0100 0000 .....?...?...... 3ffde90: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffdec0: bb00 0000 a43f 0000 a43f 0000 0100 0000 .....?...?...... 3ffded0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffdf00: bc00 0000 a53f 0000 a53f 0000 0100 0000 .....?...?...... 3ffdf10: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffdf40: bd00 0000 a63f 0000 a63f 0000 0100 0000 .....?...?...... 3ffdf50: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffdf80: be00 0000 a73f 0000 a73f 0000 0100 0000 .....?...?...... 3ffdf90: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffdfc0: bf00 0000 a83f 0000 a83f 0000 0100 0000 .....?...?...... 3ffdfd0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffe000: c000 0000 a93f 0000 a93f 0000 0100 0000 .....?...?...... 3ffe010: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffe040: c100 0000 aa3f 0000 aa3f 0000 0100 0000 .....?...?...... 3ffe050: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffe080: c200 0000 ab3f 0000 ab3f 0000 0100 0000 .....?...?...... 3ffe090: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffe0c0: c300 0000 ac3f 0000 ac3f 0000 0100 0000 .....?...?...... 3ffe0d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffe100: c400 0000 ad3f 0000 ad3f 0000 0100 0000 .....?...?...... 3ffe110: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffe140: c500 0000 ae3f 0000 ae3f 0000 0100 0000 .....?...?...... 3ffe150: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffe180: c600 0000 af3f 0000 af3f 0000 0100 0000 .....?...?...... 3ffe190: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffe1c0: c700 0000 b03f 0000 b03f 0000 0100 0000 .....?...?...... 3ffe1d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffe200: c800 0000 b13f 0000 b13f 0000 0100 0000 .....?...?...... 3ffe210: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffe240: c900 0000 b23f 0000 b23f 0000 0100 0000 .....?...?...... 3ffe250: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffe280: ca00 0000 b33f 0000 b33f 0000 0100 0000 .....?...?...... 3ffe290: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffe2c0: cb00 0000 b43f 0000 b43f 0000 0100 0000 .....?...?...... 3ffe2d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffe300: cc00 0000 b53f 0000 b53f 0000 0100 0000 .....?...?...... 3ffe310: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffe340: cd00 0000 b63f 0000 b63f 0000 0100 0000 .....?...?...... 3ffe350: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffe380: ce00 0000 b73f 0000 b73f 0000 0100 0000 .....?...?...... 3ffe390: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffe3c0: cf00 0000 b83f 0000 b83f 0000 0100 0000 .....?...?...... 3ffe3d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffe400: d000 0000 b93f 0000 b93f 0000 0100 0000 .....?...?...... 3ffe410: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffe440: d100 0000 ba3f 0000 ba3f 0000 0100 0000 .....?...?...... 3ffe450: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffe480: d200 0000 bb3f 0000 bb3f 0000 0100 0000 .....?...?...... 3ffe490: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffe4c0: d300 0000 bc3f 0000 bc3f 0000 0100 0000 .....?...?...... 3ffe4d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffe500: d400 0000 bd3f 0000 bd3f 0000 0100 0000 .....?...?...... 3ffe510: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffe540: d500 0000 be3f 0000 be3f 0000 0100 0000 .....?...?...... 3ffe550: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffe580: d600 0000 bf3f 0000 bf3f 0000 0100 0000 .....?...?...... 3ffe590: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffe5c0: d700 0000 c03f 0000 c03f 0000 0100 0000 .....?...?...... 3ffe5d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffe600: d800 0000 c13f 0000 c13f 0000 0100 0000 .....?...?...... 3ffe610: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffe640: d900 0000 c23f 0000 c23f 0000 0100 0000 .....?...?...... 3ffe650: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffe680: da00 0000 c33f 0000 c33f 0000 0100 0000 .....?...?...... 3ffe690: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffe6c0: db00 0000 c43f 0000 c43f 0000 0100 0000 .....?...?...... 3ffe6d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffe700: dc00 0000 c53f 0000 c53f 0000 0100 0000 .....?...?...... 3ffe710: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffe740: dd00 0000 c63f 0000 c63f 0000 0100 0000 .....?...?...... 3ffe750: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffe780: de00 0000 c73f 0000 c73f 0000 0100 0000 .....?...?...... 3ffe790: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffe7c0: df00 0000 c83f 0000 c83f 0000 0100 0000 .....?...?...... 3ffe7d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffe800: e000 0000 c93f 0000 c93f 0000 0100 0000 .....?...?...... 3ffe810: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffe840: e100 0000 ca3f 0000 ca3f 0000 0100 0000 .....?...?...... 3ffe850: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffe880: e200 0000 cb3f 0000 cb3f 0000 0100 0000 .....?...?...... 3ffe890: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffe8c0: e300 0000 cc3f 0000 cc3f 0000 0100 0000 .....?...?...... 3ffe8d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffe900: e400 0000 cd3f 0000 cd3f 0000 0100 0000 .....?...?...... 3ffe910: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffe940: e500 0000 ce3f 0000 ce3f 0000 0100 0000 .....?...?...... 3ffe950: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffe980: e600 0000 cf3f 0000 cf3f 0000 0100 0000 .....?...?...... 3ffe990: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffe9c0: e700 0000 d03f 0000 d03f 0000 0100 0000 .....?...?...... 3ffe9d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffea00: e800 0000 d13f 0000 d13f 0000 0100 0000 .....?...?...... 3ffea10: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffea40: e900 0000 d23f 0000 d23f 0000 0100 0000 .....?...?...... 3ffea50: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffea80: ea00 0000 d33f 0000 d33f 0000 0100 0000 .....?...?...... 3ffea90: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffeac0: eb00 0000 d43f 0000 d43f 0000 0100 0000 .....?...?...... 3ffead0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffeb00: ec00 0000 d53f 0000 d53f 0000 0100 0000 .....?...?...... 3ffeb10: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffeb40: ed00 0000 d63f 0000 d63f 0000 0100 0000 .....?...?...... 3ffeb50: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffeb80: ee00 0000 d73f 0000 d73f 0000 0100 0000 .....?...?...... 3ffeb90: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffebc0: ef00 0000 d83f 0000 d83f 0000 0100 0000 .....?...?...... 3ffebd0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffec00: f000 0000 d93f 0000 d93f 0000 0100 0000 .....?...?...... 3ffec10: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffec40: f100 0000 da3f 0000 da3f 0000 0100 0000 .....?...?...... 3ffec50: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffec80: f200 0000 db3f 0000 db3f 0000 0100 0000 .....?...?...... 3ffec90: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffecc0: f300 0000 dc3f 0000 dc3f 0000 0100 0000 .....?...?...... 3ffecd0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffed00: f400 0000 dd3f 0000 dd3f 0000 0100 0000 .....?...?...... 3ffed10: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffed40: f500 0000 de3f 0000 de3f 0000 0100 0000 .....?...?...... 3ffed50: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffed80: f600 0000 df3f 0000 df3f 0000 0100 0000 .....?...?...... 3ffed90: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffedc0: f700 0000 e03f 0000 e03f 0000 0100 0000 .....?...?...... 3ffedd0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffee00: f800 0000 e13f 0000 e13f 0000 0100 0000 .....?...?...... 3ffee10: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffee40: f900 0000 e23f 0000 e23f 0000 0100 0000 .....?...?...... 3ffee50: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffee80: fa00 0000 e33f 0000 e33f 0000 0100 0000 .....?...?...... 3ffee90: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffeec0: fb00 0000 e43f 0000 e43f 0000 0100 0000 .....?...?...... 3ffeed0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffef00: fc00 0000 e53f 0000 e53f 0000 0100 0000 .....?...?...... 3ffef10: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffef40: fd00 0000 e63f 0000 e63f 0000 0100 0000 .....?...?...... 3ffef50: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffef80: fe00 0000 e73f 0000 e73f 0000 0100 0000 .....?...?...... 3ffef90: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3ffefc0: ff00 0000 e83f 0000 e83f 0000 0100 0000 .....?...?...... 3ffefd0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3fff000: 4254 545f 4152 454e 415f 494e 464f 0000 BTT_ARENA_INFO.. 3fff010: f0d6 4d51 7ad4 44e2 8757 6b8e 8e4f 7500 ..MQz.D..Wk..Ou. 3fff020: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 3fff030: 0000 0000 0200 0000 0010 0000 e93e 0000 .............>.. 3fff040: 0010 0000 e93f 0000 0001 0000 0010 0000 .....?.......... 3fff050: 0000 0000 0000 0000 0010 0000 0000 0000 ................ 3fff060: 00b0 fe03 0000 0000 00b0 ff03 0000 0000 ................ 3fff070: 00f0 ff03 0000 0000 0000 0000 0000 0000 ................ 3fff080: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 3fffff0: 0000 0000 0000 0000 5db7 55ca 03a9 d829 ........].U....) ndctl-81/test/clear.sh000077500000000000000000000042761476737544500150530ustar00rootroot00000000000000#!/bin/bash -x # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2015-2020 Intel Corporation. All rights reserved. set -e rc=77 . $(dirname $0)/common check_min_kver "4.6" || do_skip "lacks clear poison support" trap 'err $LINENO' ERR # setup (reset nfit_test dimms) modprobe nfit_test reset rc=1 # create pmem dev="x" json=$($NDCTL create-namespace -b $NFIT_TEST_BUS0 -t pmem -m raw) eval $(echo $json | json2var) [ $dev = "x" ] && echo "fail: $LINENO" && exit 1 [ $mode != "raw" ] && echo "fail: $LINENO" && exit 1 # inject errors in the middle of the namespace, verify that reading fails err_sector="$(((size/512) / 2))" err_count=8 if ! read sector len < /sys/block/$blockdev/badblocks; then $NDCTL inject-error --block="$err_sector" --count=$err_count $dev $NDCTL start-scrub $NFIT_TEST_BUS0 && $NDCTL wait-scrub $NFIT_TEST_BUS0 fi read sector len < /sys/block/$blockdev/badblocks [ $((sector * 2)) -ne $((size /512)) ] && echo "fail: $LINENO" && exit 1 if dd if=/dev/$blockdev of=/dev/null iflag=direct bs=512 skip=$sector count=$len; then echo "fail: $LINENO" && exit 1 fi size_raw=$size sector_raw=$sector # convert pmem to fsdax mode json=$($NDCTL create-namespace -m fsdax -f -e $dev) eval $(echo $json | json2var) [ $mode != "fsdax" ] && echo "fail: $LINENO" && exit 1 # check for errors relative to the offset injected by the pfn device read sector len < /sys/block/$blockdev/badblocks [ $((sector_raw - sector)) -ne $(((size_raw - size) / 512)) ] && echo "fail: $LINENO" && exit 1 # check that writing clears the errors if ! dd of=/dev/$blockdev if=/dev/zero oflag=direct bs=512 seek=$sector count=$len; then echo "fail: $LINENO" && exit 1 fi if read sector len < /sys/block/$blockdev/badblocks; then # fail if reading badblocks returns data echo "fail: $LINENO" && exit 1 fi if check_min_kver "4.9"; then # check for re-appearance of stale badblocks from poison_list $NDCTL disable-region -b $NFIT_TEST_BUS0 all $NDCTL enable-region -b $NFIT_TEST_BUS0 all # since we have cleared the errors, a disable/reenable shouldn't bring them back if read sector len < /sys/block/$blockdev/badblocks; then # fail if reading badblocks returns data echo "fail: $LINENO" && exit 1 fi fi _cleanup exit 0 ndctl-81/test/common000066400000000000000000000053431476737544500146350ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2018, FUJITSU LIMITED. All rights reserved. # Global variables # NDCTL if [ -z $NDCTL ]; then if [ -f "../ndctl/ndctl" ] && [ -x "../ndctl/ndctl" ]; then export NDCTL=../ndctl/ndctl elif [ -f "./ndctl/ndctl" ] && [ -x "./ndctl/ndctl" ]; then export NDCTL=./ndctl/ndctl else echo "Couldn't find an ndctl binary" exit 1 fi fi # DAXCTL if [ -z $DAXCTL ]; then if [ -f "../daxctl/daxctl" ] && [ -x "../daxctl/daxctl" ]; then export DAXCTL=../daxctl/daxctl elif [ -f "./daxctl/daxctl" ] && [ -x "./daxctl/daxctl" ]; then export DAXCTL=./daxctl/daxctl else echo "Couldn't find an daxctl binary" exit 1 fi fi # CXL if [ -z $CXL ]; then if [ -f "../cxl/cxl" ] && [ -x "../cxl/cxl" ]; then export CXL=../cxl/cxl elif [ -f "./cxl/cxl" ] && [ -x "./cxl/cxl" ]; then export CXL=./cxl/cxl else echo "Couldn't find a cxl binary" exit 1 fi fi if [ -z $TEST_PATH ]; then export TEST_PATH=. fi # NFIT_TEST_BUS[01] # NFIT_TEST_BUS0="nfit_test.0" NFIT_TEST_BUS1="nfit_test.1" CXL_TEST_BUS="cxl_test" ACPI_BUS="ACPI.NFIT" E820_BUS="e820" # Functions # err # $1: line number which error detected # $2: cleanup function (optional) # err() { echo test/$(basename $0): failed at line $1 [ -n "$2" ] && "$2" exit $rc } reset() { $NDCTL disable-region -b $NFIT_TEST_BUS0 all $NDCTL init-labels -f -b $NFIT_TEST_BUS0 all $NDCTL enable-region -b $NFIT_TEST_BUS0 all } resetV() { $NDCTL disable-region -b $NFIT_TEST_BUS0 all $NDCTL init-labels -f -V 1.2 -b $NFIT_TEST_BUS0 all $NDCTL enable-region -b $NFIT_TEST_BUS0 all } reset1() { $NDCTL disable-region -b $NFIT_TEST_BUS1 all $NDCTL init-labels -f -b $NFIT_TEST_BUS1 all $NDCTL enable-region -b $NFIT_TEST_BUS1 all } # check_min_kver # $1: Supported kernel version. format: X.Y # check_min_kver() { local ver="$1" : "${KVER:=$(uname -r)}" [ -n "$ver" ] || return 1 [[ "$ver" == "$(echo -e "$ver\n$KVER" | sort -V | head -1)" ]] } # do_skip # $1: Skip message # do_skip() { echo kernel $(uname -r): $1 exit 77 } # check_prereq # $1: command to check # check_prereq() { if ! command -v "$1" >/dev/null; then do_skip "missing $1, skipping..." fi } # _cleanup # _cleanup() { $NDCTL disable-region -b $NFIT_TEST_BUS0 all $NDCTL disable-region -b $NFIT_TEST_BUS1 all modprobe -r nfit_test } _cxl_cleanup() { $NDCTL disable-region -b $CXL_TEST_BUS all modprobe -r cxl_test } # json2var # stdin: json # json2var() { sed -e "s/[{}\",]//g; s/\[//g; s/\]//g; s/:/=/g" } # check_dmesg # $1: line number where this is called check_dmesg() { # validate no WARN or lockdep report during the run sleep 1 log=$(journalctl -r -k --since "-$((SECONDS+1))s") grep -q "Call Trace" <<< $log && err $1 true } # CXL COMMON CXL_TEST_QOS_CLASS=42 ndctl-81/test/core.c000066400000000000000000000154031476737544500145140ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2015-2020 Intel Corporation. All rights reserved. #include #include #include #include #include #include #include #include #include #include #include #include #define KVER_STRLEN 20 struct ndctl_test { unsigned int kver; int attempt; int skip; }; static unsigned int get_system_kver(void) { const char *kver = getenv("KVER"); struct utsname utsname; int a, b, c; if (!kver) { uname(&utsname); kver = utsname.release; } if (sscanf(kver, "%d.%d.%d", &a, &b, &c) != 3) return LINUX_VERSION_CODE; return KERNEL_VERSION(a,b,c); } struct ndctl_test *ndctl_test_new(unsigned int kver) { struct ndctl_test *test = calloc(1, sizeof(*test)); if (!test) return NULL; if (!kver) test->kver = get_system_kver(); else test->kver = kver; return test; } int ndctl_test_result(struct ndctl_test *test, int rc) { if (ndctl_test_get_skipped(test)) fprintf(stderr, "attempted: %d skipped: %d\n", ndctl_test_get_attempted(test), ndctl_test_get_skipped(test)); if (rc && rc != 77) return rc; if (ndctl_test_get_skipped(test) >= ndctl_test_get_attempted(test)) return 77; /* return success if no failures and at least one test not skipped */ return 0; } static char *kver_str(char *buf, unsigned int kver) { snprintf(buf, KVER_STRLEN, "%d.%d.%d", (kver >> 16) & 0xffff, (kver >> 8) & 0xff, kver & 0xff); return buf; } int __ndctl_test_attempt(struct ndctl_test *test, unsigned int kver, const char *caller, int line) { char requires[KVER_STRLEN], current[KVER_STRLEN]; test->attempt++; if (kver <= test->kver) return 1; fprintf(stderr, "%s: skip %s:%d requires: %s current: %s\n", __func__, caller, line, kver_str(requires, kver), kver_str(current, test->kver)); test->skip++; return 0; } void __ndctl_test_skip(struct ndctl_test *test, const char *caller, int line) { test->skip++; test->attempt = test->skip; fprintf(stderr, "%s: explicit skip %s:%d\n", __func__, caller, line); } int ndctl_test_get_attempted(struct ndctl_test *test) { return test->attempt; } int ndctl_test_get_skipped(struct ndctl_test *test) { return test->skip; } int ndctl_test_init(struct kmod_ctx **ctx, struct kmod_module **mod, struct ndctl_ctx *nd_ctx, int log_level, struct ndctl_test *test) { int rc, family = -1; unsigned int i; const char *name; struct ndctl_bus *bus; struct log_ctx log_ctx; const char *list[] = { "nfit", "device_dax", "dax_pmem", "dax_pmem_compat", "libnvdimm", "nd_btt", "nd_e820", "nd_pmem", }; char *test_env; log_init(&log_ctx, "test/init", "NDCTL_TEST"); log_ctx.log_priority = log_level; /* * The following two checks determine the platform family. For * Intel/platforms which support ACPI, check sysfs; for other platforms * determine from the environment variable NVDIMM_TEST_FAMILY */ if (access("/sys/bus/acpi", F_OK) == 0) family = NVDIMM_FAMILY_INTEL; test_env = getenv("NDCTL_TEST_FAMILY"); if (test_env && strcmp(test_env, "PAPR") == 0) family = NVDIMM_FAMILY_PAPR; if (family == -1) { log_err(&log_ctx, "Cannot determine NVDIMM family\n"); return -ENOTSUP; } *ctx = kmod_new(NULL, NULL); if (!*ctx) return -ENXIO; kmod_set_log_priority(*ctx, log_level); /* * Check that all nfit, libnvdimm, and device-dax modules are * the mocked versions. If they are loaded, check that they have * the "out-of-tree" kernel taint, otherwise check that they * come from the "/lib/modules//extra" directory. */ for (i = 0; i < ARRAY_SIZE(list); i++) { char attr[SYSFS_ATTR_SIZE]; const char *path; char buf[100]; int state; name = list[i]; /* * Don't check for device-dax modules on kernels older * than 4.7. */ if (strcmp(name, "dax") == 0 && !ndctl_test_attempt(test, KERNEL_VERSION(4, 7, 0))) continue; /* * Skip device-dax bus-model modules on pre-v5.1 */ if ((strcmp(name, "dax_pmem_compat") == 0) && !ndctl_test_attempt(test, KERNEL_VERSION(5, 1, 0))) continue; retry: rc = kmod_module_new_from_name(*ctx, name, mod); if (rc) { log_err(&log_ctx, "failed to interrogate %s.ko\n", name); break; } path = kmod_module_get_path(*mod); if (!path) { /* * dax_pmem_compat is not required, missing is * ok, present-but-production is not ok. */ if (strcmp(name, "dax_pmem_compat") == 0) continue; if (family != NVDIMM_FAMILY_INTEL && (strcmp(name, "nfit") == 0 || strcmp(name, "nd_e820") == 0)) continue; log_err(&log_ctx, "%s.ko: failed to get path\n", name); break; } if (!strstr(path, "/extra/") && !strstr(path, "/updates/")) { log_err(&log_ctx, "%s.ko: appears to be production version: %s\n", name, path); break; } state = kmod_module_get_initstate(*mod); if (state == KMOD_MODULE_LIVE) { sprintf(buf, "/sys/module/%s/taint", name); rc = __sysfs_read_attr(&log_ctx, buf, attr); if (rc < 0) { log_err(&log_ctx, "%s.ko: failed to read %s\n", name, buf); break; } if (!strchr(attr, 'O')) { log_err(&log_ctx, "%s.ko: expected taint: O got: %s\n", name, attr); break; } } else if (state == KMOD_MODULE_BUILTIN) { log_err(&log_ctx, "%s: must be built as a module\n", name); break; } } if (i < ARRAY_SIZE(list)) { /* device-dax changed module names in 4.12 */ if (strcmp(name, "device_dax") == 0) { name = "dax"; goto retry; } kmod_unref(*ctx); return -ENXIO; } rc = kmod_module_new_from_name(*ctx, "nfit_test", mod); if (rc < 0) { kmod_unref(*ctx); return rc; } if (nd_ctx) { /* caller wants a full nfit_test reset */ ndctl_bus_foreach(nd_ctx, bus) { struct ndctl_region *region; if (strcmp(ndctl_bus_get_provider(bus), "nfit_test.0") != 0) continue; ndctl_region_foreach(bus, region) ndctl_region_disable_invalidate(region); } rc = kmod_module_remove_module(*mod, 0); if (rc < 0 && rc != -ENOENT) { kmod_unref(*ctx); return rc; } ndctl_invalidate(nd_ctx); } rc = kmod_module_probe_insert_module(*mod, KMOD_PROBE_APPLY_BLACKLIST, NULL, NULL, NULL, NULL); if (rc) kmod_unref(*ctx); if (!nd_ctx) return rc; ndctl_bus_foreach (nd_ctx, bus) { struct ndctl_region *region; struct ndctl_dimm *dimm; if (strcmp(ndctl_bus_get_provider(bus), "nfit_test.0") != 0) continue; ndctl_region_foreach (bus, region) ndctl_region_disable_invalidate(region); ndctl_dimm_foreach (bus, dimm) { ndctl_dimm_read_label_index(dimm); ndctl_dimm_init_labels(dimm, NDCTL_NS_VERSION_1_2); ndctl_dimm_disable(dimm); ndctl_dimm_enable(dimm); } ndctl_region_foreach (bus, region) ndctl_region_enable(region); } return 0; } ndctl-81/test/create.sh000077500000000000000000000020461476737544500152210ustar00rootroot00000000000000#!/bin/bash -x # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2015-2020 Intel Corporation. All rights reserved. set -e SECTOR_SIZE="4096" rc=77 . $(dirname $0)/common check_min_kver "4.5" || do_skip "may lack namespace mode attribute" trap 'err $LINENO' ERR # setup (reset nfit_test dimms) modprobe nfit_test reset rc=1 # create pmem dev="x" json=$($NDCTL create-namespace -b $NFIT_TEST_BUS0 -t pmem -m raw) eval $(echo $json | json2var ) [ $dev = "x" ] && echo "fail: $LINENO" && exit 1 [ $mode != "raw" ] && echo "fail: $LINENO" && exit 1 # convert pmem to fsdax mode json=$($NDCTL create-namespace -m fsdax -f -e $dev) eval $(echo $json | json2var) [ $mode != "fsdax" ] && echo "fail: $LINENO" && exit 1 # convert pmem to sector mode json=$($NDCTL create-namespace -m sector -l $SECTOR_SIZE -f -e $dev) eval $(echo $json | json2var) [ $sector_size != $SECTOR_SIZE ] && echo "fail: $LINENO" && exit 1 [ $mode != "sector" ] && echo "fail: $LINENO" && exit 1 # free capacity for blk creation $NDCTL destroy-namespace -f $dev _cleanup exit 0 ndctl-81/test/cxl-create-region.sh000066400000000000000000000066051476737544500172700ustar00rootroot00000000000000#!/bin/bash # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2022 Intel Corporation. All rights reserved. . $(dirname $0)/common rc=77 set -ex trap 'err $LINENO' ERR check_prereq "jq" modprobe -r cxl_test modprobe cxl_test rc=1 destroy_regions() { if [[ "$*" ]]; then $CXL destroy-region -f -b cxl_test "$@" else $CXL destroy-region -f -b cxl_test all fi } create_x1_region() { mem="$1" # find a pmem capable root decoder for this mem decoder=$($CXL list -b cxl_test -D -d root -m "$mem" | jq -r ".[] | select(.pmem_capable == true) | select(.nr_targets == 1) | .decoder") if [[ ! $decoder ]]; then echo "no suitable decoder found for $mem, skipping" return fi # create region region=$($CXL create-region -d "$decoder" -m "$mem" | jq -r ".region") if [[ ! $region ]]; then echo "create-region failed for $decoder / $mem" err "$LINENO" fi # cycle disable/enable $CXL disable-region --bus=cxl_test "$region" $CXL enable-region --bus=cxl_test "$region" # cycle destroying and creating the same region destroy_regions "$region" region=$($CXL create-region -d "$decoder" -m "$mem" | jq -r ".region") if [[ ! $region ]]; then echo "create-region failed for $decoder / $mem" err "$LINENO" fi destroy_regions "$region" } create_subregions() { slice=$((256 << 20)) mem="$1" # find a pmem capable root decoder for this mem decoder=$($CXL list -b cxl_test -D -d root -m "$mem" | jq -r ".[] | select(.pmem_capable == true) | select(.nr_targets == 1) | .decoder") if [[ ! $decoder ]]; then echo "no suitable decoder found for $mem, skipping" return fi size="$($CXL list -m "$mem" | jq -r '.[].pmem_size')" if [[ ! $size ]]; then echo "$mem: unable to determine size" err "$LINENO" fi num_regions=$((size / slice)) declare -a regions for (( i = 0; i < num_regions; i++ )); do regions[$i]=$($CXL create-region -d "$decoder" -m "$mem" -s "$slice" | jq -r ".region") if [[ ! ${regions[$i]} ]]; then echo "create sub-region failed for $decoder / $mem" err "$LINENO" fi done echo "created $num_regions subregions:" for (( i = 0; i < num_regions; i++ )); do echo "${regions[$i]}" done for (( i = (num_regions - 1); i >= 0; i-- )); do destroy_regions "${regions[$i]}" done } create_single() { # the 5th cxl_test decoder is expected to target a single-port # host-bridge. Older cxl_test implementations may not define it, # so skip the test in that case. decoder=$($CXL list -b cxl_test -D -d root | jq -r ".[4] | select(.pmem_capable == true) | select(.nr_targets == 1) | .decoder") if [[ ! $decoder ]]; then echo "no single-port host-bridge decoder found, skipping" return fi region=$($CXL create-region -d "$decoder" | jq -r ".region") if [[ ! $region ]]; then echo "failed to create single-port host-bridge region" err "$LINENO" fi destroy_regions "$region" } # test region creation on devices behind a single-port host-bridge create_single # test reading labels directly through cxl-cli readarray -t mems < <("$CXL" list -b cxl_test -M | jq -r '.[].memdev') for mem in ${mems[@]}; do create_x1_region "$mem" done # test multiple subregions under the same decoder, using slices of the same memdev # to test out back-to-back pmem DPA allocations on memdevs for mem in ${mems[@]}; do create_subregions "$mem" done check_dmesg "$LINENO" modprobe -r cxl_test ndctl-81/test/cxl-destroy-region.sh000066400000000000000000000033571476737544500175170ustar00rootroot00000000000000#!/bin/bash # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2023 Intel Corporation. All rights reserved. . $(dirname $0)/common rc=77 set -ex trap 'err $LINENO' ERR check_prereq "jq" modprobe -r cxl_test modprobe cxl_test rc=1 check_destroy_ram() { mem=$1 decoder=$2 region="$("$CXL" create-region -d "$decoder" -m "$mem" | jq -r ".region")" if [[ ! $region ]]; then err "$LINENO" fi "$CXL" enable-region "$region" # default is memory is system-ram offline "$CXL" disable-region "$region" "$CXL" destroy-region "$region" } check_destroy_devdax() { mem=$1 decoder=$2 region="$("$CXL" create-region -d "$decoder" -m "$mem" | jq -r ".region")" if [[ ! $region ]]; then err "$LINENO" fi "$CXL" enable-region "$region" dax="$("$CXL" list -X -r "$region" | jq -r ".[].daxregion.devices" | jq -r '.[].chardev')" $DAXCTL reconfigure-device -m devdax "$dax" "$CXL" disable-region "$region" "$CXL" destroy-region "$region" } # Find a memory device to create regions on to test the destroy readarray -t mems < <("$CXL" list -b "$CXL_TEST_BUS" -M | jq -r '.[].memdev') for mem in "${mems[@]}"; do ramsize="$("$CXL" list -m "$mem" | jq -r '.[].ram_size')" if [[ $ramsize == "null" || ! $ramsize ]]; then continue fi decoder="$("$CXL" list -b "$CXL_TEST_BUS" -D -d root -m "$mem" | jq -r ".[] | select(.volatile_capable == true) | select(.nr_targets == 1) | select(.max_available_extent >= ${ramsize}) | .decoder")" if [[ $decoder ]]; then check_destroy_ram "$mem" "$decoder" check_destroy_devdax "$mem" "$decoder" break fi done check_dmesg "$LINENO" modprobe -r cxl_test ndctl-81/test/cxl-events.sh000066400000000000000000000047021476737544500160440ustar00rootroot00000000000000#!/bin/bash # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2023 Intel Corporation. All rights reserved. . "$(dirname "$0")/common" # Results expected num_overflow_expected=1 num_fatal_expected=2 num_failure_expected=16 num_info_expected=3 rc=77 set -ex trap 'err $LINENO' ERR check_prereq "jq" modprobe -r cxl_test modprobe cxl_test rc=1 dev_path="/sys/bus/platform/devices" trace_path="/sys/kernel/tracing" test_region_info() { # Trigger a memdev in the cxl_test autodiscovered region region=$($CXL list -R | jq -r ".[] | .region") memdev=$($CXL list -r "$region" --targets | jq -r '.[].mappings' | jq -r '.[0].memdev') host=$($CXL list -m "$memdev" | jq -r '.[].host') echo 1 > "$dev_path"/"$host"/event_trigger if ! grep "cxl_general_media.*$region" "$trace_path"/trace; then err "$LINENO" fi if ! grep "cxl_dram.*$region" "$trace_path"/trace; then err "$LINENO" fi } test_cxl_events() { memdev="$1" if [ ! -f "${dev_path}/${memdev}/event_trigger" ]; then echo "TEST: Kernel does not support test event trigger" exit 77 fi echo "TEST: triggering $memdev" echo 1 > "${dev_path}/${memdev}/event_trigger" } readarray -t memdevs < <("$CXL" list -b cxl_test -Mi | jq -r '.[].host') echo "TEST: Prep event trace" echo "" > /sys/kernel/tracing/trace echo 1 > /sys/kernel/tracing/events/cxl/enable echo 1 > /sys/kernel/tracing/tracing_on test_cxl_events "${memdevs[0]}" echo 0 > /sys/kernel/tracing/tracing_on echo "TEST: Events seen" trace_out=$(cat /sys/kernel/tracing/trace) num_overflow=$(grep -c "cxl_overflow" <<< "${trace_out}" || true) num_fatal=$(grep -c "log=Fatal" <<< "${trace_out}" || true) num_failure=$(grep -c "log=Failure" <<< "${trace_out}" || true) num_info=$(grep -c "log=Informational" <<< "${trace_out}" || true) echo " LOG (Expected) : (Found)" echo " overflow ($num_overflow_expected) : $num_overflow" echo " Fatal ($num_fatal_expected) : $num_fatal" echo " Failure ($num_failure_expected) : $num_failure" echo " Informational ($num_info_expected) : $num_info" if [ "$num_overflow" -ne $num_overflow_expected ]; then err "$LINENO" fi if [ "$num_fatal" -ne $num_fatal_expected ]; then err "$LINENO" fi if [ "$num_failure" -ne $num_failure_expected ]; then err "$LINENO" fi if [ "$num_info" -ne $num_info_expected ]; then err "$LINENO" fi echo 1 > /sys/kernel/tracing/tracing_on test_region_info echo 0 > /sys/kernel/tracing/tracing_on check_dmesg "$LINENO" modprobe -r cxl_test ndctl-81/test/cxl-labels.sh000066400000000000000000000026111476737544500157770ustar00rootroot00000000000000#!/bin/bash # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2022 Intel Corporation. All rights reserved. . $(dirname $0)/common rc=77 set -ex trap 'err $LINENO' ERR check_prereq "jq" modprobe -r cxl_test modprobe cxl_test rc=1 test_label_ops() { nmem="$1" lsa=$(mktemp /tmp/lsa-$nmem.XXXX) lsa_read=$(mktemp /tmp/lsa-read-$nmem.XXXX) # determine LSA size "$NDCTL" read-labels -o "$lsa_read" "$nmem" lsa_size=$(stat -c %s "$lsa_read") dd "if=/dev/urandom" "of=$lsa" "bs=$lsa_size" "count=1" "$NDCTL" write-labels -i "$lsa" "$nmem" "$NDCTL" read-labels -o "$lsa_read" "$nmem" # compare what was written vs read diff "$lsa" "$lsa_read" # zero the LSA and test "$NDCTL" zero-labels "$nmem" dd "if=/dev/zero" "of=$lsa" "bs=$lsa_size" "count=1" "$NDCTL" read-labels -o "$lsa_read" "$nmem" diff "$lsa" "$lsa_read" # cleanup rm "$lsa" "$lsa_read" } test_label_ops_cxl() { mem="$1" lsa_read=$(mktemp /tmp/lsa-read-$mem.XXXX) "$CXL" read-labels -o "$lsa_read" "$mem" rm "$lsa_read" } # test reading labels directly through cxl-cli readarray -t mems < <("$CXL" list -b cxl_test -Mi | jq -r '.[].memdev') for mem in ${mems[@]}; do test_label_ops_cxl "$mem" done # find nmem devices corresponding to cxl memdevs readarray -t nmems < <("$NDCTL" list -b cxl_test -Di | jq -r '.[].dev') for nmem in ${nmems[@]}; do test_label_ops "$nmem" done check_dmesg "$LINENO" modprobe -r cxl_test ndctl-81/test/cxl-poison.sh000066400000000000000000000062251476737544500160510ustar00rootroot00000000000000#!/bin/bash # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2023 Intel Corporation. All rights reserved. . "$(dirname "$0")"/common rc=77 set -ex trap 'err $LINENO' ERR check_prereq "jq" modprobe -r cxl_test modprobe cxl_test rc=1 # THEORY OF OPERATION: Exercise cxl-cli and cxl driver ability to # inject, clear, and get the poison list. Do it by memdev and by region. find_memdev() { readarray -t capable_mems < <("$CXL" list -b "$CXL_TEST_BUS" -M | jq -r ".[] | select(.pmem_size != null) | select(.ram_size != null) | .memdev") if [ ${#capable_mems[@]} == 0 ]; then echo "no memdevs found for test" err "$LINENO" fi memdev=${capable_mems[0]} } create_x2_region() { # Find an x2 decoder decoder="$($CXL list -b "$CXL_TEST_BUS" -D -d root | jq -r ".[] | select(.pmem_capable == true) | select(.nr_targets == 2) | .decoder")" # Find a memdev for each host-bridge interleave position port_dev0="$($CXL list -T -d "$decoder" | jq -r ".[] | .targets | .[] | select(.position == 0) | .target")" port_dev1="$($CXL list -T -d "$decoder" | jq -r ".[] | .targets | .[] | select(.position == 1) | .target")" mem0="$($CXL list -M -p "$port_dev0" | jq -r ".[0].memdev")" mem1="$($CXL list -M -p "$port_dev1" | jq -r ".[0].memdev")" region="$($CXL create-region -d "$decoder" -m "$mem0" "$mem1" | jq -r ".region")" if [[ ! $region ]]; then echo "create-region failed for $decoder" err "$LINENO" fi echo "$region" } # When cxl-cli support for inject and clear arrives, replace # the writes to /sys/kernel/debug with the new cxl commands. inject_poison_sysfs() { memdev="$1" addr="$2" echo "$addr" > /sys/kernel/debug/cxl/"$memdev"/inject_poison } clear_poison_sysfs() { memdev="$1" addr="$2" echo "$addr" > /sys/kernel/debug/cxl/"$memdev"/clear_poison } validate_poison_found() { list_by="$1" nr_expect="$2" poison_list="$($CXL list "$list_by" --media-errors | jq -r '.[].media_errors')" if [[ ! $poison_list ]]; then nr_found=0 else nr_found=$(jq "length" <<< "$poison_list") fi if [ "$nr_found" -ne "$nr_expect" ]; then echo "$nr_expect poison records expected, $nr_found found" err "$LINENO" fi } test_poison_by_memdev() { find_memdev inject_poison_sysfs "$memdev" "0x40000000" inject_poison_sysfs "$memdev" "0x40001000" inject_poison_sysfs "$memdev" "0x600" inject_poison_sysfs "$memdev" "0x0" validate_poison_found "-m $memdev" 4 clear_poison_sysfs "$memdev" "0x40000000" clear_poison_sysfs "$memdev" "0x40001000" clear_poison_sysfs "$memdev" "0x600" clear_poison_sysfs "$memdev" "0x0" validate_poison_found "-m $memdev" 0 } test_poison_by_region() { create_x2_region inject_poison_sysfs "$mem0" "0x40000000" inject_poison_sysfs "$mem1" "0x40000000" validate_poison_found "-r $region" 2 clear_poison_sysfs "$mem0" "0x40000000" clear_poison_sysfs "$mem1" "0x40000000" validate_poison_found "-r $region" 0 } # Turn tracing on. Note that 'cxl list --media-errors' toggles the tracing. # Turning it on here allows the test user to also view inject and clear # trace events. echo 1 > /sys/kernel/tracing/events/cxl/cxl_poison/enable test_poison_by_memdev test_poison_by_region check_dmesg "$LINENO" modprobe -r cxl-test ndctl-81/test/cxl-qos-class.sh000077500000000000000000000046321476737544500164520ustar00rootroot00000000000000#!/bin/bash # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2024 Intel Corporation. All rights reserved. . $(dirname $0)/common rc=77 set -ex trap 'err $LINENO' ERR check_prereq "jq" modprobe -r cxl_test modprobe cxl_test rc=1 check_qos_decoders () { # check root decoders have expected fake qos_class # also make sure the number of root decoders equal to the number # with qos_class found json=$($CXL list -b cxl_test -D -d root) num_decoders=$(echo "$json" | jq length) count=0 while read -r qos_class; do if [[ "$qos_class" != "$CXL_TEST_QOS_CLASS" ]]; then err "$LINENO" fi count=$((count+1)) done <<< "$(echo "$json" | jq -r '.[] | .qos_class')" if [[ "$count" != "$num_decoders" ]]; then err "$LINENO" fi } check_qos_memdevs () { # Check that memdevs that expose ram_qos_class or pmem_qos_class have # expected fake value programmed. json=$($CXL list -b cxl_test -M) num_memdevs=$(echo "$json" | jq length) for (( i = 0; i < num_memdevs; i++ )); do ram_size="$(jq ".[$i] | .ram_size" <<< "$json")" ram_qos_class="$(jq ".[$i] | .ram_qos_class" <<< "$json")" pmem_size="$(jq ".[$i] | .pmem_size" <<< "$json")" pmem_qos_class="$(jq ".[$i] | .pmem_qos_class" <<< "$json")" if [[ "$ram_size" != null ]] && ((ram_qos_class != CXL_TEST_QOS_CLASS)); then err "$LINENO" fi if [[ "$pmem_size" != null ]] && ((pmem_qos_class != CXL_TEST_QOS_CLASS)); then err "$LINENO" fi done } # Based on cxl-create-region.sh create_single() destroy_regions() { if [[ "$*" ]]; then $CXL destroy-region -f -b cxl_test "$@" else $CXL destroy-region -f -b cxl_test all fi } create_region_check_qos() { # Find an x1 decoder decoder=$($CXL list -b cxl_test -D -d root | jq -r "[ .[] | select(.max_available_extent > 0) | select(.pmem_capable == true) | select(.nr_targets == 1) ] | .[0].decoder") # Find a memdev for this host-bridge port_dev0="$("$CXL" list -T -d "$decoder" | jq -r ".[] | .targets | .[] | select(.position == 0) | .target")" mem0="$("$CXL" list -M -p "$port_dev0" | jq -r ".[0].memdev")" memdevs="$mem0" # Send create-region with -Q to enforce qos_class matching region="$("$CXL" create-region -Q -d "$decoder" -m "$memdevs" | jq -r ".region")" if [[ ! $region ]]; then echo "failed to create region" err "$LINENO" fi destroy_regions "$region" } check_qos_decoders check_qos_memdevs create_region_check_qos check_dmesg "$LINEO" modprobe -r cxl_test ndctl-81/test/cxl-region-sysfs.sh000066400000000000000000000132171476737544500171710ustar00rootroot00000000000000#!/bin/bash # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2022 Intel Corporation. All rights reserved. . $(dirname $0)/common rc=77 set -ex trap 'err $LINENO' ERR check_prereq "jq" modprobe -r cxl_test modprobe cxl_test rc=1 # THEORY OF OPERATION: Create a x8 interleave across the pmem capacity # of the 8 endpoints defined by cxl_test, commit the decoders (which # just stubs out the actual hardware programming aspect, but updates the # driver state), and then tear it all down again. As with other cxl_test # tests if the CXL topology in tools/testing/cxl/test/cxl.c ever changes # then the paired update must be made to this test. # find the root decoder that spans both test host-bridges and support pmem decoder=$($CXL list -b cxl_test -D -d root | jq -r ".[] | select(.pmem_capable == true) | select(.nr_targets == 2) | .decoder") # find the memdevs mapped by that decoder readarray -t mem < <($CXL list -M -d $decoder | jq -r ".[].memdev") # ask cxl reserve-dpa to allocate pmem capacity from each of those memdevs readarray -t endpoint < <($CXL reserve-dpa -t pmem ${mem[*]} -s $((256<<20)) | jq -r ".[] | .decoder.decoder") # instantiate an empty region region=$(cat /sys/bus/cxl/devices/$decoder/create_pmem_region) echo $region > /sys/bus/cxl/devices/$decoder/create_pmem_region uuidgen > /sys/bus/cxl/devices/$region/uuid # setup interleave geometry nr_targets=${#endpoint[@]} echo $nr_targets > /sys/bus/cxl/devices/$region/interleave_ways r_ig=$(cat /sys/bus/cxl/devices/$decoder/interleave_granularity) echo $r_ig > /sys/bus/cxl/devices/$region/interleave_granularity echo $((nr_targets * (256<<20))) > /sys/bus/cxl/devices/$region/size # grab the list of memdevs grouped by host-bridge interleave position port_dev0=$($CXL list -T -d $decoder | jq -r ".[] | .targets | .[] | select(.position == 0) | .target") port_dev1=$($CXL list -T -d $decoder | jq -r ".[] | .targets | .[] | select(.position == 1) | .target") readarray -t mem_sort0 < <($CXL list -M -p $port_dev0 | jq -r ".[] | .memdev") readarray -t mem_sort1 < <($CXL list -M -p $port_dev1 | jq -r ".[] | .memdev") # TODO: add a cxl list option to list memdevs in valid region provisioning # order, hardcode for now. mem_sort=() mem_sort[0]=${mem_sort0[0]} mem_sort[1]=${mem_sort1[0]} mem_sort[2]=${mem_sort0[2]} mem_sort[3]=${mem_sort1[2]} mem_sort[4]=${mem_sort0[1]} mem_sort[5]=${mem_sort1[1]} mem_sort[6]=${mem_sort0[3]} mem_sort[7]=${mem_sort1[3]} # TODO: use this alternative memdev ordering to validate a negative test for # specifying invalid positions of memdevs #mem_sort[2]=${mem_sort0[0]} #mem_sort[1]=${mem_sort1[0]} #mem_sort[0]=${mem_sort0[2]} #mem_sort[3]=${mem_sort1[2]} #mem_sort[4]=${mem_sort0[1]} #mem_sort[5]=${mem_sort1[1]} #mem_sort[6]=${mem_sort0[3]} #mem_sort[7]=${mem_sort1[3]} # re-generate the list of endpoint decoders in region position programming order endpoint=() for i in ${mem_sort[@]} do readarray -O ${#endpoint[@]} -t endpoint < <($CXL list -Di -d endpoint -m $i | jq -r ".[] | select(.mode == \"pmem\") | .decoder") done # attach all endpoint decoders to the region pos=0 for i in ${endpoint[@]} do echo $i > /sys/bus/cxl/devices/$region/target$pos pos=$((pos+1)) done echo "$region added ${#endpoint[@]} targets: ${endpoint[@]}" # validate all endpoint decoders have the correct setting region_size=$(cat /sys/bus/cxl/devices/$region/size) region_base=$(cat /sys/bus/cxl/devices/$region/resource) for i in ${endpoint[@]} do iw=$(cat /sys/bus/cxl/devices/$i/interleave_ways) ig=$(cat /sys/bus/cxl/devices/$i/interleave_granularity) [ $iw -ne $nr_targets ] && err "$LINENO: decoder: $i iw: $iw targets: $nr_targets" [ $ig -ne $r_ig ] && err "$LINENO: decoder: $i ig: $ig root ig: $r_ig" sz=$(cat /sys/bus/cxl/devices/$i/size) res=$(cat /sys/bus/cxl/devices/$i/start) [[ $sz -ne $region_size ]] && err "$LINENO: decoder: $i sz: $sz region_size: $region_size" [[ $res -ne $region_base ]] && err "$LINENO: decoder: $i base: $res region_base: $region_base" done # validate all switch decoders have the correct settings nr_switches=$((nr_targets/2)) nr_host_bridges=$((nr_switches/2)) nr_switch_decoders=$((nr_switches + nr_host_bridges)) json=$($CXL list -D -r $region -d switch) readarray -t switch_decoders < <(echo $json | jq -r ".[].decoder") [ ${#switch_decoders[@]} -ne $nr_switch_decoders ] && err \ "$LINENO: expected $nr_switch_decoders got ${#switch_decoders[@]} switch decoders" for i in ${switch_decoders[@]} do decoder=$(echo $json | jq -r ".[] | select(.decoder == \"$i\")") id=${i#decoder} port_id=${id%.*} depth=$($CXL list -p $port_id -S | jq -r ".[].depth") iw=$(echo $decoder | jq -r ".interleave_ways") ig=$(echo $decoder | jq -r ".interleave_granularity") [ $iw -ne 2 ] && err "$LINENO: decoder: $i iw: $iw targets: 2" [ $ig -ne $((r_ig << depth)) ] && err \ "$LINENO: decoder: $i ig: $ig switch_ig: $((r_ig << depth))" res=$(echo $decoder | jq -r ".resource") sz=$(echo $decoder | jq -r ".size") [[ $sz -ne $region_size ]] && err \ "$LINENO: decoder: $i sz: $sz region_size: $region_size" [[ $res -ne $region_base ]] && err \ "$LINENO: decoder: $i base: $res region_base: $region_base" done # walk up the topology and commit all decoders echo 1 > /sys/bus/cxl/devices/$region/commit # walk down the topology and de-commit all decoders echo 0 > /sys/bus/cxl/devices/$region/commit # remove endpoints from the region pos=0 for i in ${endpoint[@]} do echo "" > /sys/bus/cxl/devices/$region/target$pos pos=$((pos+1)) done # release DPA capacity readarray -t endpoint < <($CXL free-dpa -t pmem ${mem[*]} | jq -r ".[] | .decoder.decoder") echo "$region released ${#endpoint[@]} targets: ${endpoint[@]}" check_dmesg "$LINENO" modprobe -r cxl_test ndctl-81/test/cxl-sanitize.sh000066400000000000000000000032131476737544500163620ustar00rootroot00000000000000#!/bin/bash # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2022 Intel Corporation. All rights reserved. . $(dirname $0)/common rc=77 set -ex trap 'err $LINENO' ERR check_prereq "jq" modprobe -r cxl_test modprobe cxl_test rc=1 # THEORY OF OPERATION: Find a memdev with programmed decoders, validate # that sanitize requests fail. Find a memdev without programmed # decoders, validate that submission succeeds, and validate that the # notifier fires. mem_to_host() { host=$("$CXL" list -m $1 | jq -r '.[].host') echo $host } set_timeout() { host=$(mem_to_host $1) echo $2 > /sys/bus/platform/devices/$host/sanitize_timeout } # find all memdevs readarray -t all_mem < <("$CXL" list -b cxl_test -M | jq -r '.[].memdev') # try to sanitize an active memdev readarray -t active_mem < <("$CXL" list -b cxl_test -RT | jq -r '.[].mappings[].memdev') count=${#active_mem[@]} ((count > 0)) || err $LINENO # set timeout to 2 seconds set_timeout ${active_mem[0]} 2000 # sanitize with an active memdev should fail echo 1 > /sys/bus/cxl/devices/${active_mem[0]}/security/sanitize && err $LINENO # find an inactive mem inactive="" for mem in ${all_mem[@]}; do inactive=$mem for active in ${active_mem[@]}; do if [ $mem = $active ]; then inactive="" fi done if [ -z $inactive ]; then continue; fi break done [ -z $inactive ] && err $LINENO # kickoff a background sanitize and make sure the wait takes a couple # secounds set_timeout $inactive 3000 start=$SECONDS echo 1 > /sys/bus/cxl/devices/${inactive}/security/sanitize & "$CXL" wait-sanitize $inactive || err $LINENO ((SECONDS > start + 2)) || err $LINENO check_dmesg "$LINENO" modprobe -r cxl_test ndctl-81/test/cxl-security000066400000000000000000000022061476737544500157730ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2022, Intel Corp. All rights reserved. detect() { dev="$($NDCTL list -b "$CXL_TEST_BUS" -D | jq -r 'sort_by(.id) | .[0].dev')" [ -n "$dev" ] || err "$LINENO" id="$($NDCTL list -b "$CXL_TEST_BUS" -D | jq -r 'sort_by(.id) | .[0].id')" [ -n "$id" ] || err "$LINENO" } lock_dimm() { $NDCTL disable-dimm "$dev" test_dimm_path="" nmem_rpath=$(readlink -f "/sys/bus/nd/devices/${dev}") nmem_bus=$(dirname ${nmem_rpath}); bus_provider_path="${nmem_bus}/provider" test -e "$bus_provider_path" || err "$LINENO" bus_provider=$(cat ${bus_provider_path}) [[ "$bus_provider" == "$CXL_TEST_BUS" ]] || err "$LINENO" bus="cxl" nmem_provider_path="/sys/bus/nd/devices/${dev}/${bus}/provider" nmem_provider=$(cat ${nmem_provider_path}) test_dimm_path=$(readlink -f /sys/bus/$bus/devices/${nmem_provider}) test_dimm_path=$(dirname $(dirname ${test_dimm_path}))/security_lock test -e "$test_dimm_path" # now lock the dimm echo 1 > "${test_dimm_path}" sstate="$(get_security_state)" if [ "$sstate" != "locked" ]; then echo "Incorrect security state: $sstate expected: locked" err "$LINENO" fi } ndctl-81/test/cxl-security.sh000077500000000000000000000002171476737544500164070ustar00rootroot00000000000000#!/bin/bash -Ex # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2022 Intel Corporation. All rights reserved. $(dirname $0)/security.sh cxl ndctl-81/test/cxl-topology.sh000066400000000000000000000127261476737544500164210ustar00rootroot00000000000000#!/bin/bash # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2022 Intel Corporation. All rights reserved. . $(dirname $0)/common rc=77 set -ex trap 'err $LINENO' ERR check_prereq "jq" modprobe -r cxl_test modprobe cxl_test rc=1 # THEORY OF OPERATION: Validate the hard coded assumptions of the # cxl_test.ko module that defines its topology in # tools/testing/cxl/test/cxl.c. If that model ever changes then the # paired update must be made to this test. # validate the autodiscovered region region=$("$CXL" list -R | jq -r ".[] | .region") if [[ ! $region ]]; then echo "failed to find autodiscovered region" err "$LINENO" fi # collect cxl_test root device id json=$($CXL list -b cxl_test) count=$(jq "length" <<< $json) ((count == 1)) || err "$LINENO" root=$(jq -r ".[] | .bus" <<< $json) # validate 2 or 3 host bridges under a root port port_sort="sort_by(.port | .[4:] | tonumber)" json=$($CXL list -b cxl_test -BP) count=$(jq ".[] | .[\"ports:$root\"] | length" <<< $json) ((count == 2)) || ((count == 3)) || err "$LINENO" bridges=$count bridge[0]=$(jq -r ".[] | .[\"ports:$root\"] | $port_sort | .[0].port" <<< $json) bridge[1]=$(jq -r ".[] | .[\"ports:$root\"] | $port_sort | .[1].port" <<< $json) ((bridges > 2)) && bridge[2]=$(jq -r ".[] | .[\"ports:$root\"] | $port_sort | .[2].port" <<< $json) # validate root ports per host bridge check_host_bridge() { json=$($CXL list -b cxl_test -T -p $1) count=$(jq ".[] | .dports | length" <<< $json) ((count == $2)) || err "$3" } check_host_bridge ${bridge[0]} 2 $LINENO check_host_bridge ${bridge[1]} 2 $LINENO ((bridges > 2)) && check_host_bridge ${bridge[2]} 1 $LINENO # validate 2 switches per root-port json=$($CXL list -b cxl_test -P -p ${bridge[0]}) count=$(jq ".[] | .[\"ports:${bridge[0]}\"] | length" <<< $json) ((count == 2)) || err "$LINENO" switch[0]=$(jq -r ".[] | .[\"ports:${bridge[0]}\"] | $port_sort | .[0].host" <<< $json) switch[1]=$(jq -r ".[] | .[\"ports:${bridge[0]}\"] | $port_sort | .[1].host" <<< $json) json=$($CXL list -b cxl_test -P -p ${bridge[1]}) count=$(jq ".[] | .[\"ports:${bridge[1]}\"] | length" <<< $json) ((count == 2)) || err "$LINENO" switch[2]=$(jq -r ".[] | .[\"ports:${bridge[1]}\"] | $port_sort | .[0].host" <<< $json) switch[3]=$(jq -r ".[] | .[\"ports:${bridge[1]}\"] | $port_sort | .[1].host" <<< $json) # validate the expected properties of the 4 or 5 root decoders # use the size of the first decoder to determine the # cxl_test version / properties json=$($CXL list -b cxl_test -D -d root) port_id=${root:4} port_id_len=${#port_id} decoder_sort="sort_by(.decoder | .[$((8+port_id_len)):] | tonumber)" count=$(jq "[ $decoder_sort | .[0] | select(.volatile_capable == true) | select(.size == $((256 << 20))) | select(.nr_targets == 1) ] | length" <<< $json) if [ $count -eq 1 ]; then decoder_base_size=$((256 << 20)) pmem_size=$((256 << 20)) else decoder_base_size=$((1 << 30)) pmem_size=$((1 << 30)) fi count=$(jq "[ $decoder_sort | .[1] | select(.volatile_capable == true) | select(.size == $((decoder_base_size * 2))) | select(.nr_targets == 2) ] | length" <<< $json) ((count == 1)) || err "$LINENO" count=$(jq "[ $decoder_sort | .[2] | select(.pmem_capable == true) | select(.size == $decoder_base_size) | select(.nr_targets == 1) ] | length" <<< $json) ((count == 1)) || err "$LINENO" count=$(jq "[ $decoder_sort | .[3] | select(.pmem_capable == true) | select(.size == $((decoder_base_size * 2))) | select(.nr_targets == 2) ] | length" <<< $json) ((count == 1)) || err "$LINENO" if (( bridges == 3 )); then count=$(jq "[ $decoder_sort | .[4] | select(.pmem_capable == true) | select(.size == $decoder_base_size) | select(.nr_targets == 1) ] | length" <<< $json) ((count == 1)) || err "$LINENO" fi # check that all 8 or 10 cxl_test memdevs are enabled by default and have a # pmem size of 256M, or 1G json=$($CXL list -b cxl_test -M) count=$(jq "map(select(.pmem_size == $pmem_size)) | length" <<< $json) ((bridges == 2 && count == 8 || bridges == 3 && count == 10 || bridges == 4 && count == 11)) || err "$LINENO" # check that switch ports disappear after all of their memdevs have been # disabled, and return when the memdevs are enabled. for s in ${switch[@]} do json=$($CXL list -M -p $s) count=$(jq "length" <<< $json) ((count == 2)) || err "$LINENO" mem[0]=$(jq -r ".[0] | .memdev" <<< $json) mem[1]=$(jq -r ".[1] | .memdev" <<< $json) $CXL disable-memdev ${mem[0]} --force json=$($CXL list -p $s) count=$(jq "length" <<< $json) ((count == 1)) || err "$LINENO" $CXL disable-memdev ${mem[1]} --force json=$($CXL list -p $s) count=$(jq "length" <<< $json) ((count == 0)) || err "$LINENO" $CXL enable-memdev ${mem[0]} $CXL enable-memdev ${mem[1]} json=$($CXL list -p $s) count=$(jq "length" <<< $json) ((count == 1)) || err "$LINENO" $CXL disable-port $s --force json=$($CXL list -p $s) count=$(jq "length" <<< $json) ((count == 0)) || err "$LINENO" $CXL enable-memdev ${mem[0]} ${mem[1]} json=$($CXL list -p $s) count=$(jq "length" <<< $json) ((count == 1)) || err "$LINENO" done # validate host bridge tear down for the first 2 bridges for b in ${bridge[0]} ${bridge[1]} do $CXL disable-port $b -f json=$($CXL list -M -i -p $b) count=$(jq "map(select(.state == \"disabled\")) | length" <<< $json) ((count == 4)) || err "$LINENO" $CXL enable-port $b -m json=$($CXL list -M -p $b) count=$(jq "length" <<< $json) ((count == 4)) || err "$LINENO" done # validate that the bus can be disabled without issue $CXL disable-bus $root -f check_dmesg "$LINENO" modprobe -r cxl_test ndctl-81/test/cxl-update-firmware.sh000077500000000000000000000071161476737544500176410ustar00rootroot00000000000000#!/bin/bash # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2023 Intel Corporation. All rights reserved. . $(dirname $0)/common rc=77 set -ex trap 'err $LINENO' ERR check_prereq "jq" check_prereq "dd" check_prereq "sha256sum" modprobe -r cxl_test modprobe cxl_test rc=1 mk_fw_file() { size="$1" if [[ ! $size ]]; then err "$LINENO" fi if (( size > 64 )); then err "$LINENO" fi fw_file="$(mktemp -p /tmp fw_file_XXXX)" dd if=/dev/urandom of="$fw_file" bs=1M count="$size" echo "$fw_file" } find_memdevs() { count="$1" if [[ ! $count ]]; then count=1 fi "$CXL" list -M -b "$CXL_TEST_BUS" \ | jq -r '.[] | select(.host | startswith("cxl_mem.")) | .memdev' \ | head -"$count" } do_update_fw() { "$CXL" update-firmware -b "$CXL_TEST_BUS" "$@" } wait_complete() { mem="$1" # single memdev, not a list max_wait="$2" # in seconds waited=0 while true; do json="$("$CXL" list -m "$mem" -F)" in_prog="$(jq -r '.[].firmware.fw_update_in_progress' <<< "$json")" if [[ $in_prog == "true" ]]; then sleep 1 waited="$((waited + 1))" continue else break fi if (( waited == max_wait )); then echo "completion timeout for $mem" err "$LINENO" fi done } validate_json_state() { json="$1" state="$2" while read -r in_prog_state; do if [[ $in_prog_state == $state ]]; then continue else echo "expected fw_update_in_progress:$state" err "$LINENO" fi done < <(jq -r '.[].firmware.fw_update_in_progress' <<< "$json") } validate_fw_update_in_progress() { validate_json_state "$1" "true" } validate_fw_update_idle() { validate_json_state "$1" "false" } validate_staged_slot() { json="$1" slot="$2" while read -r staged_slot; do if [[ $staged_slot == $slot ]]; then continue else echo "expected staged_slot:$slot" err "$LINENO" fi done < <(jq -r '.[].firmware.staged_slot' <<< "$json") } check_sha() { mem="$1" host=$($CXL list -m $mem | jq -r '.[].host') file="$2" csum_path="/sys/bus/platform/devices/${host}/fw_buf_checksum" mem_csum="$(cat "$csum_path")" file_csum="$(sha256sum "$file" | awk '{print $1}')" if [[ $mem_csum != $file_csum ]]; then echo "checksum failure for mem=$mem" err "$LINENO" fi } test_blocking_update() { file="$(mk_fw_file 8)" mem="$(find_memdevs 1)" json=$(do_update_fw -F "$file" --wait "$mem") validate_fw_update_idle "$json" # cxl_test's starting slot is '2', so staged should be 3 validate_staged_slot "$json" 3 check_sha "$mem" "$file" rm "$file" } test_nonblocking_update() { file="$(mk_fw_file 16)" mem="$(find_memdevs 1)" json=$(do_update_fw -F "$file" "$mem") validate_fw_update_in_progress "$json" wait_complete "$mem" 15 validate_fw_update_idle "$("$CXL" list -m "$mem" -F)" check_sha "$mem" "$file" rm "$file" } test_multiple_memdev() { num_mems=2 file="$(mk_fw_file 16)" declare -a mems mems=( $(find_memdevs "$num_mems") ) json="$(do_update_fw -F "$file" "${mems[@]}")" validate_fw_update_in_progress "$json" # use the in-band wait this time json="$(do_update_fw --wait "${mems[@]}")" validate_fw_update_idle "$json" for mem in ${mems[@]}; do check_sha "$mem" "$file" done rm "$file" } test_cancel() { file="$(mk_fw_file 16)" mem="$(find_memdevs 1)" json=$(do_update_fw -F "$file" "$mem") validate_fw_update_in_progress "$json" do_update_fw --cancel "$mem" # cancellation is asynchronous, and the result looks the same as idle wait_complete "$mem" 15 validate_fw_update_idle "$("$CXL" list -m "$mem" -F)" # no need to check_sha rm "$file" } test_blocking_update test_nonblocking_update test_multiple_memdev test_cancel check_dmesg "$LINENO" modprobe -r cxl_test ndctl-81/test/cxl-xor-region.sh000066400000000000000000000070051476737544500166300ustar00rootroot00000000000000#!/bin/bash # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2022 Intel Corporation. All rights reserved. . $(dirname $0)/common rc=77 set -ex trap 'err $LINENO' ERR check_prereq "jq" modprobe -r cxl_test modprobe cxl_test interleave_arithmetic=1 udevadm settle rc=1 # THEORY OF OPERATION: Create x1,2,3,4 regions to exercise the XOR math # option of the CXL driver. As with other cxl_test tests, changes to the # CXL topology in tools/testing/cxl/test/cxl.c may require an update here. create_and_destroy_region() { region=$($CXL create-region -d "$decoder" -m "$memdevs" | jq -r ".region") if [[ ! $region ]]; then echo "create-region failed for $decoder" err "$LINENO" fi $CXL destroy-region -f -b cxl_test "$region" } setup_x1() { # Find an x1 decoder decoder=$($CXL list -b cxl_test -D -d root | jq -r ".[] | select(.pmem_capable == true) | select(.nr_targets == 1) | .decoder") # Find a memdev for this host-bridge port_dev0=$($CXL list -T -d "$decoder" | jq -r ".[] | .targets | .[] | select(.position == 0) | .target") mem0=$($CXL list -M -p "$port_dev0" | jq -r ".[0].memdev") memdevs="$mem0" } setup_x2() { # Find an x2 decoder decoder=$($CXL list -b cxl_test -D -d root | jq -r ".[] | select(.pmem_capable == true) | select(.nr_targets == 2) | .decoder") # Find a memdev for each host-bridge interleave position port_dev0=$($CXL list -T -d "$decoder" | jq -r ".[] | .targets | .[] | select(.position == 0) | .target") port_dev1=$($CXL list -T -d "$decoder" | jq -r ".[] | .targets | .[] | select(.position == 1) | .target") mem0=$($CXL list -M -p "$port_dev0" | jq -r ".[0].memdev") mem1=$($CXL list -M -p "$port_dev1" | jq -r ".[0].memdev") memdevs="$mem0 $mem1" } setup_x4() { # find an x2 decoder decoder=$($CXL list -b cxl_test -D -d root | jq -r ".[] | select(.pmem_capable == true) | select(.nr_targets == 2) | .decoder") # Find a memdev for each host-bridge interleave position port_dev0=$($CXL list -T -d "$decoder" | jq -r ".[] | .targets | .[] | select(.position == 0) | .target") port_dev1=$($CXL list -T -d "$decoder" | jq -r ".[] | .targets | .[] | select(.position == 1) | .target") mem0=$($CXL list -M -p "$port_dev0" | jq -r ".[0].memdev") mem1=$($CXL list -M -p "$port_dev1" | jq -r ".[0].memdev") mem2=$($CXL list -M -p "$port_dev0" | jq -r ".[1].memdev") mem3=$($CXL list -M -p "$port_dev1" | jq -r ".[1].memdev") memdevs="$mem0 $mem1 $mem2 $mem3" } setup_x3() { # find an x3 decoder decoder=$($CXL list -b cxl_test -D -d root | jq -r ".[] | select(.pmem_capable == true) | select(.nr_targets == 3) | .decoder") if [[ ! $decoder ]]; then echo "no x3 decoder found, skipping xor-x3 test" return fi # Find a memdev for each host-bridge interleave position port_dev0=$($CXL list -T -d "$decoder" | jq -r ".[] | .targets | .[] | select(.position == 0) | .target") port_dev1=$($CXL list -T -d "$decoder" | jq -r ".[] | .targets | .[] | select(.position == 1) | .target") port_dev2=$($CXL list -T -d "$decoder" | jq -r ".[] | .targets | .[] | select(.position == 2) | .target") mem0=$($CXL list -M -p "$port_dev0" | jq -r ".[0].memdev") mem1=$($CXL list -M -p "$port_dev1" | jq -r ".[0].memdev") mem2=$($CXL list -M -p "$port_dev2" | jq -r ".[0].memdev") memdevs="$mem0 $mem1 $mem2" } setup_x1 create_and_destroy_region setup_x2 create_and_destroy_region setup_x4 create_and_destroy_region # x3 decoder may not be available in cxl/test topo yet setup_x3 if [[ $decoder ]]; then create_and_destroy_region fi check_dmesg "$LINENO" modprobe -r cxl_test ndctl-81/test/dax-dev.c000066400000000000000000000054071476737544500151170ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-2.1 // Copyright (C) 2014-2020, Intel Corporation. All rights reserved. #include #include #include #include #include #include #include #include #include #include #include #include #include struct ndctl_namespace *ndctl_get_test_dev(struct ndctl_ctx *ctx) { char path[256]; const char *bdev; int fd, rc = -ENXIO; struct ndctl_bus *bus; struct ndctl_dax *dax; struct ndctl_pfn *pfn; struct ndctl_namespace *ndns; enum ndctl_namespace_mode mode; struct ndctl_region *region, *min = NULL; bus = ndctl_bus_get_by_provider(ctx, "e820"); if (!bus) goto out; ndctl_region_foreach(bus, region) { if (!min) { min = region; continue; } if (ndctl_region_get_id(region) < ndctl_region_get_id(min)) min = region; } if (!min) goto out; region = min; /* attempt to re-enable the region if a previous test disabled it */ ndctl_region_enable(region); ndns = ndctl_namespace_get_first(region); if (!ndns) goto out; rc = ndctl_namespace_enable(ndns); if (rc) goto out; mode = ndctl_namespace_get_mode(ndns); if (mode >= 0 && mode != NDCTL_NS_MODE_MEMORY) goto out; /* if device-dax mode already established it might contain user data */ pfn = ndctl_namespace_get_pfn(ndns); dax = ndctl_namespace_get_dax(ndns); if (dax || pfn) goto out; /* device is unconfigured, assume that was on purpose */ bdev = ndctl_namespace_get_block_device(ndns); if (!bdev) goto out; if (snprintf(path, sizeof(path), "/dev/%s", bdev) >= (int) sizeof(path)) goto out; /* * Note, if the bdev goes active after this check we'll still * clobber it in the following tests, see test/dax.sh and * test/device-dax.sh. */ fd = open(path, O_RDWR | O_EXCL); if (fd < 0) goto out; close(fd); rc = 0; out: return rc ? NULL : ndns; } static int emit_e820_device(int loglevel, struct ndctl_test *test) { int err; struct ndctl_ctx *ctx; struct ndctl_namespace *ndns; if (!ndctl_test_attempt(test, KERNEL_VERSION(4, 3, 0))) return 77; err = ndctl_new(&ctx); if (err < 0) return err; ndctl_set_log_priority(ctx, loglevel); ndns = ndctl_get_test_dev(ctx); if (!ndns) { fprintf(stderr, "%s: failed to find usable victim device\n", __func__); ndctl_test_skip(test); err = 77; } else { fprintf(stdout, "%s\n", ndctl_namespace_get_devname(ndns)); err = 0; } ndctl_unref(ctx); return err; } int __attribute__((weak)) main(int argc, char *argv[]) { struct ndctl_test *test = ndctl_test_new(0); int rc; if (!test) { fprintf(stderr, "failed to initialize test\n"); return EXIT_FAILURE; } rc = emit_e820_device(LOG_DEBUG, test); return ndctl_test_result(test, rc); } ndctl-81/test/dax-errors.c000066400000000000000000000055001476737544500156470ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2015-2020 Intel Corporation. All rights reserved. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define fail() fprintf(stderr, "%s: failed at: %d\n", __func__, __LINE__) static sigjmp_buf sj_env; static int sig_count; /* buf is global in order to avoid gcc memcpy optimization */ static void *buf; static void sigbus_hdl(int sig, siginfo_t *siginfo, void *ptr) { fprintf(stderr, "** Received a SIGBUS **\n"); sig_count++; siglongjmp(sj_env, 1); } static int test_dax_read_err(int fd) { void *base; int rc = 0; if (fd < 0) { fail(); return -ENXIO; } if (posix_memalign(&buf, 4096, 4096) != 0) return -ENOMEM; base = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); if (base == MAP_FAILED) { perror("mmap"); rc = -ENXIO; goto err_mmap; } if (sigsetjmp(sj_env, 1)) { if (sig_count == 1) { fprintf(stderr, "Failed to read from mapped file\n"); free(buf); if (base) { if (munmap(base, 4096) < 0) { fail(); return 1; } } return 1; } return sig_count; } /* read a page through DAX (should fail due to a bad block) */ memcpy(buf, base, 4096); err_mmap: free(buf); return rc; } /* TODO: disabled till we get clear-on-write in the kernel */ #if 0 static int test_dax_write_clear(int fd) { void *buf; int rc = 0; if (fd < 0) { fail(); return -ENXIO; } if (posix_memalign(&buf, 4096, 4096) != 0) return -ENOMEM; memset(buf, 0, 4096); /* * Attempt to write zeroes to the first page of the file using write() * This should clear the pmem errors/bad blocks */ printf("Attempting to write\n"); if (write(fd, buf, 4096) < 0) rc = errno; free(buf); return rc; } #endif int main(int argc, char *argv[]) { int fd, rc; struct sigaction act; if (argc < 1) return -EINVAL; memset(&act, 0, sizeof(act)); act.sa_sigaction = sigbus_hdl; act.sa_flags = SA_SIGINFO; if (sigaction(SIGBUS, &act, 0)) { fail(); return 1; } fd = open(argv[1], O_RDWR | O_DIRECT); /* Start the test. First, we do an mmap-read, and expect it to fail */ rc = test_dax_read_err(fd); if (rc == 0) { fprintf(stderr, "Expected read to fail, but it succeeded\n"); rc = -ENXIO; goto out; } if (rc > 1) { fprintf(stderr, "Received a second SIGBUS, exiting.\n"); rc = -ENXIO; goto out; } printf(" mmap-read failed as expected\n"); rc = 0; /* Next, do a regular (O_DIRECT) write() */ /* TODO: Disable this till we have clear-on-write in the kernel * rc = test_dax_write_clear(fd); * * if (rc) * perror("write"); */ out: if (fd >= 0) close(fd); return rc; } ndctl-81/test/dax-ext4.sh000077700000000000000000000000001476737544500165122dax.shustar00rootroot00000000000000ndctl-81/test/dax-pmd.c000066400000000000000000000200131476737544500151070ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2015-2020 Intel Corporation. All rights reserved. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define NUM_EXTENTS 5 #define fail() fprintf(stderr, "%s: failed at: %d (%s)\n", \ __func__, __LINE__, strerror(errno)) #define faili(i) fprintf(stderr, "%s: failed at: %d: %d (%s)\n", \ __func__, __LINE__, i, strerror(errno)) #define TEST_DIR "test_dax_mnt" #define TEST_FILE TEST_DIR "/test_dax_data" #define REGION_MEM_SIZE 4096*4 #define REGION_PM_SIZE 4096*512 #define REMAP_SIZE 4096 static sigjmp_buf sj_env; static void sigbus(int sig, siginfo_t *siginfo, void *d) { siglongjmp(sj_env, 1); } int test_dax_remap(struct ndctl_test *test, int dax_fd, unsigned long align, void *dax_addr, off_t offset, bool fsdax) { void *anon, *remap, *addr; struct sigaction act; int rc, val; if ((fsdax || align == SZ_2M) && !ndctl_test_attempt(test, KERNEL_VERSION(5, 8, 0))) { /* kernel's prior to 5.8 may crash on this test */ fprintf(stderr, "%s: SKIP mremap() test\n", __func__); return 0; } anon = mmap(NULL, REGION_MEM_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); addr = mmap(dax_addr, 2*align, PROT_READ|PROT_WRITE, MAP_SHARED, dax_fd, offset); fprintf(stderr, "%s: addr: %p size: %#lx\n", __func__, addr, 2*align); if (addr == MAP_FAILED) { rc = -errno; faili(0); return rc; } memset(anon, 'a', REGION_MEM_SIZE); memset(addr, 'i', align*2); remap = mremap(addr, REMAP_SIZE, REMAP_SIZE, MREMAP_MAYMOVE|MREMAP_FIXED, anon); if (remap == MAP_FAILED) { fprintf(stderr, "%s: mremap failed, that's ok too\n", __func__); return 0; } if (remap != anon) { rc = -ENXIO; perror("mremap"); faili(1); return rc; } fprintf(stderr, "%s: addr: %p size: %#x\n", __func__, remap, REMAP_SIZE); memset(&act, 0, sizeof(act)); act.sa_sigaction = sigbus; act.sa_flags = SA_SIGINFO; if (sigaction(SIGBUS, &act, 0)) { perror("sigaction"); return EXIT_FAILURE; } /* test fault after device-dax instance disabled */ if (sigsetjmp(sj_env, 1)) { if (!fsdax && align > SZ_4K) { fprintf(stderr, "got expected SIGBUS after mremap() of device-dax\n"); return 0; } else { fprintf(stderr, "unpexpected SIGBUS after mremap()\n"); return -EIO; } } *(int *) anon = 0xAA; val = *(int *) anon; if (val != 0xAA) { faili(2); return -ENXIO; } return 0; } int test_dax_directio(int dax_fd, unsigned long align, void *dax_addr, off_t offset) { int i, rc = -ENXIO; void *buf; if (posix_memalign(&buf, 4096, 4096) != 0) return -ENOMEM; for (i = 0; i < 5; i++) { unsigned long flags; void *addr; int fd2; if (dax_fd >= 0) flags = MAP_SHARED; else { /* hugetlbfs instead of device-dax */ const char *base = "/sys/kernel/mm/hugepages"; FILE *f_nrhuge; char path[256]; flags = MAP_SHARED | MAP_ANONYMOUS; if (align >= SZ_2M) { char setting[] = { "2\n" }; sprintf(path, "%s/hugepages-%ldkB/nr_hugepages", base, align / 1024); f_nrhuge = fopen(path, "r+"); if (!f_nrhuge) { rc = -errno; faili(i); return rc; } if (fwrite(setting, sizeof(setting), 1, f_nrhuge) != 1) { rc = -errno; faili(i); fclose(f_nrhuge); return rc; } fclose(f_nrhuge); /* FIXME: support non-x86 page sizes */ if (align > SZ_2M) flags |= MAP_HUGETLB | MAP_HUGE_1GB; else flags |= MAP_HUGETLB | MAP_HUGE_2MB; } } addr = mmap(dax_addr, 2*align, PROT_READ|PROT_WRITE, flags, dax_fd, offset); if (addr == MAP_FAILED) { rc = -errno; faili(i); break; } rc = -ENXIO; rc = mkdir(TEST_DIR, 0600); if (rc < 0 && errno != EEXIST) { faili(i); munmap(addr, 2 * align); break; } fd2 = open(TEST_FILE, O_CREAT|O_TRUNC|O_DIRECT|O_RDWR, 0600); if (fd2 < 0) { faili(i); munmap(addr, 2*align); break; } fprintf(stderr, "%s: test: %d\n", __func__, i); rc = 0; switch (i) { case 0: /* test O_DIRECT read of unfaulted address */ if (write(fd2, addr, 4096) != 4096) { faili(i); rc = -ENXIO; } /* * test O_DIRECT write of pre-faulted read-only * address */ if (pread(fd2, addr, 4096, 0) != 4096) { faili(i); rc = -ENXIO; } break; case 1: /* test O_DIRECT of pre-faulted address */ sprintf(addr, "odirect data"); if (pwrite(fd2, addr, 4096, 0) != 4096) { faili(i); rc = -ENXIO; } ((char *) buf)[0] = 0; if (pread(fd2, buf, 4096, 0) != 4096) { faili(i); rc = -ENXIO; } if (strcmp(buf, "odirect data") != 0) { faili(i); rc = -ENXIO; } break; case 2: /* fork with pre-faulted pmd */ sprintf(addr, "fork data"); rc = fork(); if (rc == 0) { /* child */ if (strcmp(addr, "fork data") == 0) exit(EXIT_SUCCESS); else exit(EXIT_FAILURE); } else if (rc > 0) { /* parent */ wait(&rc); rc = WEXITSTATUS(rc); if (rc != EXIT_SUCCESS) { faili(i); } } else faili(i); break; case 3: /* convert ro mapping to rw */ rc = *(volatile int *) addr; *(volatile int *) addr = rc; rc = 0; break; case 4: /* test O_DIRECT write of unfaulted address */ sprintf(buf, "O_DIRECT write of unfaulted address\n"); if (pwrite(fd2, buf, 4096, 0) < 4096) { faili(i); rc = -ENXIO; break; } if (pread(fd2, addr, 4096, 0) < 4096) { faili(i); rc = -ENXIO; break; } rc = 0; break; default: faili(i); rc = -ENXIO; break; } munmap(addr, 2*align); addr = MAP_FAILED; unlink(TEST_FILE); close(fd2); fd2 = -1; if (rc) break; } free(buf); return rc; } /* test_pmd assumes that fd references a pre-allocated + dax-capable file */ static int test_pmd(struct ndctl_test *test, int fd) { unsigned long long m_align, p_align, pmd_off; static const bool fsdax = true; struct fiemap_extent *ext; void *base, *pmd_addr; struct fiemap *map; int rc = -ENXIO; unsigned long i; if (fd < 0) { fail(); return -ENXIO; } map = calloc(1, sizeof(struct fiemap) + sizeof(struct fiemap_extent) * NUM_EXTENTS); if (!map) { fail(); return -ENXIO; } base = mmap(NULL, 4*HPAGE_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); if (base == MAP_FAILED) { fail(); goto err_mmap; } munmap(base, 4*HPAGE_SIZE); map->fm_start = 0; map->fm_length = -1; map->fm_extent_count = NUM_EXTENTS; rc = ioctl(fd, FS_IOC_FIEMAP, map); if (rc < 0) { fail(); goto err_extent; } for (i = 0; i < map->fm_mapped_extents; i++) { ext = &map->fm_extents[i]; p_align = ALIGN(ext->fe_physical, HPAGE_SIZE) - ext->fe_physical; fprintf(stderr, "[%ld]: l: %llx p: %llx len: %llx flags: %x\n", i, ext->fe_logical, ext->fe_physical, ext->fe_length, ext->fe_flags); if (ext->fe_length > 2 * HPAGE_SIZE && p_align == 0) { fprintf(stderr, "found potential huge extent\n"); break; } } if (i >= map->fm_mapped_extents) { fail(); goto err_extent; } m_align = ALIGN(base, HPAGE_SIZE) - ((unsigned long) base); p_align = ALIGN(ext->fe_physical, HPAGE_SIZE) - ext->fe_physical; pmd_addr = (char *) base + m_align; pmd_off = ext->fe_logical + p_align; rc = test_dax_remap(test, fd, HPAGE_SIZE, pmd_addr, pmd_off, fsdax); if (rc) goto err_test; rc = test_dax_directio(fd, HPAGE_SIZE, pmd_addr, pmd_off); if (rc) goto err_test; rc = test_dax_poison(test, fd, HPAGE_SIZE, pmd_addr, pmd_off, fsdax); err_test: err_extent: err_mmap: free(map); return rc; } int __attribute__((weak)) main(int argc, char *argv[]) { struct ndctl_test *test = ndctl_test_new(0); int fd, rc; if (!test) { fprintf(stderr, "failed to initialize test\n"); return EXIT_FAILURE; } if (argc < 1) return -EINVAL; fd = open(argv[1], O_RDWR); rc = test_pmd(test, fd); if (fd >= 0) close(fd); return ndctl_test_result(test, rc); } ndctl-81/test/dax-poison.c000066400000000000000000000066271476737544500156550ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2018-2020 Intel Corporation. All rights reserved. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define fail() fprintf(stderr, "%s: failed at: %d (%s)\n", \ __func__, __LINE__, strerror(errno)) static sigjmp_buf sj_env; static int sig_mcerr_ao, sig_mcerr_ar, sig_count; static void sigbus_hdl(int sig, siginfo_t *si, void *ptr) { switch (si->si_code) { case BUS_MCEERR_AO: fprintf(stderr, "%s: BUS_MCEERR_AO addr: %p len: %d\n", __func__, si->si_addr, 1 << si->si_addr_lsb); sig_mcerr_ao++; break; case BUS_MCEERR_AR: fprintf(stderr, "%s: BUS_MCEERR_AR addr: %p len: %d\n", __func__, si->si_addr, 1 << si->si_addr_lsb); sig_mcerr_ar++; break; default: sig_count++; break; } siglongjmp(sj_env, 1); } int test_dax_poison(struct ndctl_test *test, int dax_fd, unsigned long align, void *dax_addr, off_t offset, bool fsdax) { unsigned char *addr = MAP_FAILED; struct sigaction act; unsigned x = x; FILE *smaps; void *buf; int rc; if (!ndctl_test_attempt(test, KERNEL_VERSION(4, 19, 0))) return 77; /* * MADV_HWPOISON must be page aligned, and this routine assumes * align is >= 8K */ if (align < SZ_2M) return 0; if (posix_memalign(&buf, 4096, 4096) != 0) return -ENOMEM; memset(&act, 0, sizeof(act)); act.sa_sigaction = sigbus_hdl; act.sa_flags = SA_SIGINFO; if (sigaction(SIGBUS, &act, 0)) { fail(); rc = -errno; goto out; } /* dirty the block on disk to bypass the default zero page */ if (fsdax) { rc = pwrite(dax_fd, buf, 4096, offset + align / 2); if (rc < 4096) { fail(); rc = -ENXIO; goto out; } fsync(dax_fd); } addr = mmap(dax_addr, 2*align, PROT_READ|PROT_WRITE, MAP_SHARED_VALIDATE|MAP_POPULATE|MAP_SYNC, dax_fd, offset); if (addr == MAP_FAILED) { fail(); rc = -errno; goto out; } fprintf(stderr, "%s: mmap got %p align: %ld offset: %zd\n", __func__, addr, align, offset); if (sigsetjmp(sj_env, 1)) { if (sig_mcerr_ar) { fprintf(stderr, "madvise triggered 'action required' sigbus\n"); goto clear_error; } else if (sig_count) { fail(); return -ENXIO; } } rc = madvise(addr + align / 2, 4096, MADV_SOFT_OFFLINE); if (rc == 0) { fprintf(stderr, "softoffline should always fail for dax\n"); smaps = fopen("/proc/self/smaps", "r"); do { rc = fread(buf, 1, 4096, smaps); fwrite(buf, 1, rc, stderr); } while (rc); fclose(smaps); fail(); rc = -ENXIO; goto out; } rc = madvise(addr + align / 2, 4096, MADV_HWPOISON); if (rc) { fail(); rc = -errno; goto out; } /* clear the error */ clear_error: if (!sig_mcerr_ar) { fail(); rc = -ENXIO; goto out; } if (!fsdax) { rc = 0; goto out; } rc = fallocate(dax_fd, FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE, offset + align / 2, 4096); if (rc) { fail(); rc = -errno; goto out; } rc = pwrite(dax_fd, buf, 4096, offset + align / 2); if (rc < 4096) { fail(); rc = -ENXIO; goto out; } fsync(dax_fd); /* check that we can fault in the poison page */ x = *(volatile unsigned *) addr + align / 2; rc = 0; out: if (addr != MAP_FAILED) munmap(addr, 2 * align); free(buf); return rc; } ndctl-81/test/dax-xfs.sh000077700000000000000000000000001476737544500164262dax.shustar00rootroot00000000000000ndctl-81/test/dax.sh000077500000000000000000000053671476737544500145430ustar00rootroot00000000000000#!/bin/bash -x # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2015-2020 Intel Corporation. All rights reserved. . $(dirname $0)/common MNT=test_dax_mnt FILE=image blockdev="" cleanup() { echo "test-dax: failed at line $1" if [ -n "$blockdev" ]; then umount /dev/$blockdev else rc=77 fi rm -rf $MNT exit $rc } run_test() { rc=0 if ! trace-cmd record -e fs_dax:dax_pmd_fault_done $TEST_PATH/dax-pmd $MNT/$FILE; then rc=$? if [ "$rc" -ne 77 ] && [ "$rc" -ne 0 ]; then cleanup "$1" fi fi # Fragile hack to double check the kernel services this test # with successful pmd faults. If dax-pmd.c ever changes the # number of times the dax_pmd_fault_done trace point fires the # hack needs to be updated from 10 expected firings and the # result of success (NOPAGE). count=0 rc=1 while read -r p; do [[ $p ]] || continue if [ "$count" -lt 10 ]; then if [ "$p" != "0x100" ] && [ "$p" != "NOPAGE" ]; then cleanup "$1" fi fi count=$((count + 1)) done < <(trace-cmd report | awk '{ print $21 }') if [ $count -lt 10 ]; then cleanup "$1" fi } run_ext4() { mkfs.ext4 -b 4096 /dev/$blockdev mount /dev/$blockdev $MNT -o dax fallocate -l 1GiB $MNT/$FILE run_test $LINENO umount $MNT # convert pmem to put the memmap on the device json=$($NDCTL create-namespace -m fsdax -M dev -f -e $dev) eval $(json2var <<< "$json") [ $mode != "fsdax" ] && echo "fail: $LINENO" && exit 1 #note the blockdev returned from ndctl create-namespace lacks the /dev prefix mkfs.ext4 -b 4096 /dev/$blockdev mount /dev/$blockdev $MNT -o dax fallocate -l 1GiB $MNT/$FILE run_test $LINENO umount $MNT json=$($NDCTL create-namespace -m raw -f -e $dev) eval $(json2var <<< "$json") [ $mode != "fsdax" ] && echo "fail: $LINENO" && exit 1 true } run_xfs() { mkfs.xfs -f -d su=2m,sw=1,agcount=2 -m reflink=0 /dev/$blockdev mount /dev/$blockdev $MNT -o dax fallocate -l 1GiB $MNT/$FILE run_test $LINENO umount $MNT # convert pmem to put the memmap on the device json=$($NDCTL create-namespace -m fsdax -M dev -f -e $dev) eval $(json2var <<< "$json") [ $mode != "fsdax" ] && echo "fail: $LINENO" && exit 1 mkfs.xfs -f -d su=2m,sw=1,agcount=2 -m reflink=0 /dev/$blockdev mount /dev/$blockdev $MNT -o dax fallocate -l 1GiB $MNT/$FILE run_test $LINENO umount $MNT # revert namespace to raw mode json=$($NDCTL create-namespace -m raw -f -e $dev) eval $(json2var <<< "$json") [ $mode != "fsdax" ] && echo "fail: $LINENO" && exit 1 true } set -e mkdir -p $MNT trap 'err $LINENO cleanup' ERR dev=$($TEST_PATH/dax-dev) json=$($NDCTL list -N -n $dev) eval $(json2var <<< "$json") rc=1 if [ $(basename $0) = "dax-ext4.sh" ]; then run_ext4 elif [ $(basename $0) = "dax-xfs.sh" ]; then run_xfs else run_ext4 run_xfs fi check_dmesg "$LINENO" exit 0 ndctl-81/test/daxctl-create.sh000077500000000000000000000223111476737544500164730ustar00rootroot00000000000000#!/bin/bash -Ex # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2020, Oracle Corporation. rc=77 . $(dirname $0)/common trap 'cleanup $LINENO' ERR modprobe -r cxl_test modprobe cxl_test $CXL list cleanup() { printf "Error at line %d\n" "$1" [[ $testdev ]] && reset_dax exit $rc } find_testdev() { local rc=77 # find a victim region provided by cxl_test region_id="$("$DAXCTL" list -R | jq -r ".[] | select(.path | contains(\"cxl_acpi.0\")) | .id")" if [[ ! "$region_id" ]]; then printf "Unable to find a victim region\n" exit "$rc" fi # find a victim device testdev=$("$DAXCTL" list -D -r "$region_id" | jq -er '.[0].chardev | .//""') if [[ ! $testdev ]]; then printf "Unable to find a victim device\n" exit "$rc" fi printf "Found victim dev: %s on region id 0\n" "$testdev" } setup_dev() { local rc=1 local nmaps=0 test -n "$testdev" nmaps=$(daxctl_get_nr_mappings "$testdev") if [[ $nmaps == 0 ]]; then printf "Device is idle" exit "$rc" fi "$DAXCTL" reconfigure-device -m devdax -f "$testdev" "$DAXCTL" disable-device "$testdev" "$DAXCTL" reconfigure-device -s 0 "$testdev" available=$("$DAXCTL" list -r "$region_id" | jq -er '.[0].available_size | .//""') } reset_dev() { test -n "$testdev" "$DAXCTL" disable-device "$testdev" "$DAXCTL" reconfigure-device -s "$available" "$testdev" "$DAXCTL" enable-device "$testdev" } reset_dax() { test -n "$testdev" "$DAXCTL" disable-device -r "$region_id" all "$DAXCTL" destroy-device -r "$region_id" all "$DAXCTL" reconfigure-device -s "$available" "$testdev" } clear_dev() { "$DAXCTL" disable-device "$testdev" "$DAXCTL" reconfigure-device -s 0 "$testdev" } test_pass() { local rc=1 # Available size _available_size=$("$DAXCTL" list -r "$region_id" | jq -er '.[0].available_size | .//""') if [[ ! $_available_size == "$available" ]]; then echo "Unexpected available size $_available_size != $available" exit "$rc" fi } fail_if_available() { local rc=1 _size=$("$DAXCTL" list -r "$region_id" | jq -er '.[0].available_size | .//""') if [[ $_size ]]; then echo "Unexpected available size $_size" exit "$rc" fi } daxctl_get_dev() { "$DAXCTL" list -d "$1" | jq -er '.[].chardev' } daxctl_get_mode() { "$DAXCTL" list -d "$1" | jq -er '.[].mode' } daxctl_get_size_by_mapping() { local size=0 local _start=0 local _end=0 _start=$(cat "$1"/start) _end=$(cat "$1"/end) ((size=size + _end - _start + 1)) echo $size } daxctl_get_nr_mappings() { local i=0 local _size=0 local devsize=0 local path="" path=$(readlink -f /sys/bus/dax/devices/"$1"/) until ! [ -d "$path/mapping$i" ] do _size=$(daxctl_get_size_by_mapping "$path/mapping$i") if [[ $_size == 0 ]]; then i=0 break fi ((devsize=devsize + _size)) ((i=i + 1)) done # Return number of mappings if the sizes between size field # and the one computed by mappingNNN are the same _size=$("$DAXCTL" list -d "$1" | jq -er '.[0].size | .//""') if [[ ! $_size == "$devsize" ]]; then echo 0 else echo $i fi } daxctl_test_multi() { local daxdev size=$((available / 4)) if [[ $2 ]]; then "$DAXCTL" disable-device "$testdev" "$DAXCTL" reconfigure-device -s $size "$testdev" fi daxdev_1=$("$DAXCTL" create-device -r "$region_id" -s $size | jq -er '.[].chardev') test -n "$daxdev_1" daxdev_2=$("$DAXCTL" create-device -r "$region_id" -s $size | jq -er '.[].chardev') test -n "$daxdev_2" if [[ ! $2 ]]; then daxdev_3=$("$DAXCTL" create-device -r "$region_id" -s $size | jq -er '.[].chardev') test -n "$daxdev_3" fi # Hole "$DAXCTL" disable-device "$1" "$DAXCTL" destroy-device "$1" # Pick space in the created hole and at the end new_size=$((size * 2)) daxdev_4=$("$DAXCTL" create-device -r "$region_id" -s "$new_size" | jq -er '.[].chardev') test -n "$daxdev_4" test "$(daxctl_get_nr_mappings "$daxdev_4")" -eq 2 fail_if_available "$DAXCTL" disable-device -r "$region_id" all "$DAXCTL" destroy-device -r "$region_id" all } daxctl_test_multi_reconfig() { local ncfgs=$1 local dump=$2 local daxdev size=$((available / ncfgs)) test -n "$testdev" "$DAXCTL" disable-device "$testdev" "$DAXCTL" reconfigure-device -s $size "$testdev" "$DAXCTL" disable-device "$testdev" daxdev_1=$("$DAXCTL" create-device -r "$region_id" -s $size | jq -er '.[].chardev') "$DAXCTL" disable-device "$daxdev_1" start=$((size + size)) max=$((size * ncfgs / 2)) for i in $(seq $start $size $max) do "$DAXCTL" disable-device "$testdev" "$DAXCTL" reconfigure-device -s "$i" "$testdev" "$DAXCTL" disable-device "$daxdev_1" "$DAXCTL" reconfigure-device -s "$i" "$daxdev_1" done test "$(daxctl_get_nr_mappings "$testdev")" -eq $((ncfgs / 2)) test "$(daxctl_get_nr_mappings "$daxdev_1")" -eq $((ncfgs / 2)) if [[ $dump ]]; then "$DAXCTL" list -M -d "$daxdev_1" | jq -er '.[]' > "$dump" fi fail_if_available "$DAXCTL" disable-device "$daxdev_1" && "$DAXCTL" destroy-device "$daxdev_1" } daxctl_test_adjust() { local rc=1 local ncfgs=4 local daxdev size=$((available / ncfgs)) test -n "$testdev" start=$((size + size)) for i in $(seq 1 1 $ncfgs) do daxdev=$("$DAXCTL" create-device -r "$region_id" -s "$size" | jq -er '.[].chardev') test "$(daxctl_get_nr_mappings "$daxdev")" -eq 1 done daxdev=$(daxctl_get_dev "dax$region_id.1") "$DAXCTL" disable-device "$daxdev" && "$DAXCTL" destroy-device "$daxdev" daxdev=$(daxctl_get_dev "dax$region_id.4") "$DAXCTL" disable-device "$daxdev" && "$DAXCTL" destroy-device "$daxdev" daxdev=$(daxctl_get_dev "dax$region_id.2") "$DAXCTL" disable-device "$daxdev" "$DAXCTL" reconfigure-device -s $((size * 2)) "$daxdev" # Allocates space at the beginning: expect two mappings as # as don't adjust the mappingX region. This is because we # preserve the relative page_offset of existing allocations test "$(daxctl_get_nr_mappings "$daxdev")" -eq 2 daxdev=$(daxctl_get_dev "dax$region_id.3") "$DAXCTL" disable-device "$daxdev" "$DAXCTL" reconfigure-device -s $((size * 2)) "$daxdev" # Adjusts space at the end, expect one mapping because we are # able to extend existing region range. test "$(daxctl_get_nr_mappings "$daxdev")" -eq 1 fail_if_available daxdev=$(daxctl_get_dev "dax$region_id.3") "$DAXCTL" disable-device "$daxdev" && "$DAXCTL" destroy-device "$daxdev" daxdev=$(daxctl_get_dev "dax$region_id.2") "$DAXCTL" disable-device "$daxdev" && "$DAXCTL" destroy-device "$daxdev" } # Test 0: # Sucessfully zero out the region device and allocate the whole space again. daxctl_test0() { clear_dev test_pass } # Test 1: # Sucessfully creates and destroys a device with the whole available space daxctl_test1() { local daxdev daxdev=$("$DAXCTL" create-device -r "$region_id" | jq -er '.[].chardev') test -n "$daxdev" test "$(daxctl_get_nr_mappings "$daxdev")" -eq 1 fail_if_available "$DAXCTL" disable-device "$daxdev" && "$DAXCTL" destroy-device "$daxdev" clear_dev test_pass } # Test 2: space at the middle and at the end # Successfully pick space in the middle and space at the end, by # having the region device reconfigured with some of the memory. daxctl_test2() { daxctl_test_multi "$region_id.1" 1 clear_dev test_pass } # Test 3: space at the beginning and at the end # Successfully pick space in the beginning and space at the end, by # having the region device emptied (so region beginning starts with daxX.1). daxctl_test3() { daxctl_test_multi "$region_id.1" clear_dev test_pass } # Test 4: space at the end # Successfully reconfigure two devices in increasingly bigger allocations. # The difference is that it reuses an existing resource, and only needs to # pick at the end of the region daxctl_test4() { daxctl_test_multi_reconfig 8 "" clear_dev test_pass } # Test 5: space adjust # Successfully adjusts two resources to fill the whole region # First adjusts towards the beginning of region, the second towards the end. daxctl_test5() { daxctl_test_adjust clear_dev test_pass } # Test 6: align # Successfully creates a device with a align property daxctl_test6() { local daxdev local align local size # Available size size=$available # Use 2M by default or 1G if supported align=2097152 if (( available >= 1073741824 )); then align=1073741824 size=$align fi daxdev=$("$DAXCTL" create-device -r "$region_id" -s $size -a $align | jq -er '.[].chardev') test -n "$daxdev" "$DAXCTL" disable-device "$daxdev" && "$DAXCTL" destroy-device "$daxdev" clear_dev test_pass } # Test 7: input device # Successfully creates a device with an input file from the multi-range # device test, and checking that we have the same number of mappings/size. daxctl_test7() { daxctl_test_multi_reconfig 8 "input.json" # The parameter should parse the region_id from the chardev entry # therefore using the same region_id as test4 daxdev_1=$("$DAXCTL" create-device --input input.json | jq -er '.[].chardev') # Validate if it's the same mappings as done by test4 # It also validates the size computed from the mappings # A zero value means it failed, and four mappings is what's # created by daxctl_test4 test "$(daxctl_get_nr_mappings "$daxdev_1")" -eq 4 "$DAXCTL" disable-device "$daxdev_1" && "$DAXCTL" destroy-device "$daxdev_1" clear_dev test_pass } find_testdev rc=1 setup_dev daxctl_test0 daxctl_test1 daxctl_test2 daxctl_test3 daxctl_test4 daxctl_test5 daxctl_test6 daxctl_test7 reset_dev modprobe -r cxl_test exit 0 ndctl-81/test/daxctl-devices.sh000077500000000000000000000070471476737544500166630ustar00rootroot00000000000000#!/bin/bash -Ex # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2019-2020 Intel Corporation. All rights reserved. rc=77 . $(dirname $0)/common trap 'cleanup $LINENO' ERR cleanup() { printf "Error at line %d\n" "$1" [[ $testdev ]] && reset_dev exit $rc } find_testdev() { local rc=77 # The kmem driver is needed to change the device mode, only # kernels >= v5.1 might have it available. Skip if not. if ! modinfo kmem; then # check if kmem is builtin if ! grep -qF "kmem" "/lib/modules/$(uname -r)/modules.builtin"; then printf "Unable to find kmem module\n" exit $rc fi fi # find a victim device testbus="$ACPI_BUS" testdev=$("$NDCTL" list -b "$testbus" -Ni | jq -er '.[0].dev | .//""') if [[ ! $testdev ]]; then printf "Unable to find a victim device\n" exit "$rc" fi printf "Found victim dev: %s on bus: %s\n" "$testdev" "$testbus" } setup_dev() { test -n "$testbus" test -n "$testdev" "$NDCTL" destroy-namespace -f -b "$testbus" "$testdev" # x86_64 memory hotplug can require up to a 2GiB-aligned chunk # of memory. Create a 4GiB namespace, so that we will still have # enough room left after aligning the start and end. testdev=$("$NDCTL" create-namespace -b "$testbus" -m devdax -fe "$testdev" -s 4G | \ jq -er '.dev') test -n "$testdev" } reset_dev() { "$NDCTL" destroy-namespace -f -b "$testbus" "$testdev" } daxctl_get_dev() { "$NDCTL" list -n "$1" -X | jq -er '.[].daxregion.devices[0].chardev' } daxctl_get_mode() { "$DAXCTL" list -d "$1" | jq -er '.[].mode' } set_online_policy() { echo "online" > /sys/devices/system/memory/auto_online_blocks } unset_online_policy() { echo "offline" > /sys/devices/system/memory/auto_online_blocks } save_online_policy() { saved_policy="$(cat /sys/devices/system/memory/auto_online_blocks)" } restore_online_policy() { echo "$saved_policy" > /sys/devices/system/memory/auto_online_blocks } daxctl_test() { local daxdev daxdev=$(daxctl_get_dev "$testdev") test -n "$daxdev" # these tests need to run with kernel onlining policy turned off save_online_policy unset_online_policy "$DAXCTL" reconfigure-device -N -m system-ram "$daxdev" [[ $(daxctl_get_mode "$daxdev") == "system-ram" ]] "$DAXCTL" online-memory "$daxdev" "$DAXCTL" offline-memory "$daxdev" "$DAXCTL" reconfigure-device -m devdax "$daxdev" [[ $(daxctl_get_mode "$daxdev") == "devdax" ]] "$DAXCTL" reconfigure-device -m system-ram "$daxdev" [[ $(daxctl_get_mode "$daxdev") == "system-ram" ]] "$DAXCTL" reconfigure-device -f -m devdax "$daxdev" [[ $(daxctl_get_mode "$daxdev") == "devdax" ]] # fail 'ndctl-disable-namespace' while the devdax namespace is active # as system-ram. If this test fails, a reboot will be required to # recover from the resulting state. test -n "$testdev" "$DAXCTL" reconfigure-device -m system-ram "$daxdev" [[ $(daxctl_get_mode "$daxdev") == "system-ram" ]] if ! "$NDCTL" disable-namespace "$testdev"; then echo "disable-namespace failed as expected" else echo "disable-namespace succeded, expected failure" echo "reboot required to recover from this state" return 1 fi "$DAXCTL" reconfigure-device -f -m devdax "$daxdev" [[ $(daxctl_get_mode "$daxdev") == "devdax" ]] # this tests for reconfiguration failure if an online-policy is set set_online_policy : "This command is expected to fail:" if ! "$DAXCTL" reconfigure-device -N -m system-ram "$daxdev"; then echo "reconfigure failed as expected" else echo "reconfigure succeded, expected failure" restore_online_policy return 1 fi restore_online_policy } find_testdev setup_dev rc=1 daxctl_test reset_dev exit 0 ndctl-81/test/daxdev-errors.c000066400000000000000000000200151476737544500163440ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2015-2020 Intel Corporation. All rights reserved. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define fail() fprintf(stderr, "%s: failed at: %d\n", __func__, __LINE__) struct check_cmd { struct ndctl_cmd *cmd; struct ndctl_test *test; }; static sigjmp_buf sj_env; static int sig_count; static const char *NFIT_PROVIDER0 = "nfit_test.0"; static struct check_cmd *check_cmds; static void sigbus_hdl(int sig, siginfo_t *siginfo, void *ptr) { fprintf(stderr, "** Received a SIGBUS **\n"); sig_count++; siglongjmp(sj_env, 1); } static int check_ars_cap(struct ndctl_bus *bus, uint64_t start, size_t size, struct check_cmd *check) { struct ndctl_cmd *cmd; int rc; if (check->cmd != NULL) { fprintf(stderr, "%s: expected a NULL command, by default\n", __func__); return -EINVAL; } cmd = ndctl_bus_cmd_new_ars_cap(bus, start, size); if (!cmd) { fprintf(stderr, "%s: bus: %s failed to create cmd\n", __func__, ndctl_bus_get_provider(bus)); return -ENOTTY; } rc = ndctl_cmd_submit(cmd); if (rc < 0) { fprintf(stderr, "%s: bus: %s failed to submit cmd: %d\n", __func__, ndctl_bus_get_provider(bus), rc); ndctl_cmd_unref(cmd); return rc; } if (ndctl_cmd_ars_cap_get_size(cmd) < sizeof(struct nd_cmd_ars_status)){ fprintf(stderr, "%s: bus: %s expected size >= %zd got: %d\n", __func__, ndctl_bus_get_provider(bus), sizeof(struct nd_cmd_ars_status), ndctl_cmd_ars_cap_get_size(cmd)); ndctl_cmd_unref(cmd); return -ENXIO; } check->cmd = cmd; return 0; } static int check_ars_start(struct ndctl_bus *bus, struct check_cmd *check) { struct ndctl_cmd *cmd_ars_cap = check_cmds[ND_CMD_ARS_CAP].cmd; struct ndctl_cmd *cmd; int rc; if (check->cmd != NULL) { fprintf(stderr, "%s: expected a NULL command, by default\n", __func__); return -ENXIO; } cmd = ndctl_bus_cmd_new_ars_start(cmd_ars_cap, ND_ARS_PERSISTENT); if (!cmd) { fprintf(stderr, "%s: bus: %s failed to create cmd\n", __func__, ndctl_bus_get_provider(bus)); return -ENOTTY; } rc = ndctl_cmd_submit(cmd); if (rc < 0) { fprintf(stderr, "%s: bus: %s failed to submit cmd: %d\n", __func__, ndctl_bus_get_provider(bus), rc); ndctl_cmd_unref(cmd); return rc; } check->cmd = cmd; return 0; } static int check_ars_status(struct ndctl_bus *bus, struct check_cmd *check) { struct ndctl_cmd *cmd_ars_cap = check_cmds[ND_CMD_ARS_CAP].cmd; struct ndctl_cmd *cmd; unsigned long tmo = 5; unsigned int i; int rc; if (check->cmd != NULL) { fprintf(stderr, "%s: expected a NULL command, by default\n", __func__); return -ENXIO; } retry: cmd = ndctl_bus_cmd_new_ars_status(cmd_ars_cap); if (!cmd) { fprintf(stderr, "%s: bus: %s failed to create cmd\n", __func__, ndctl_bus_get_provider(bus)); return -ENOTTY; } rc = ndctl_cmd_submit(cmd); if (rc < 0) { fprintf(stderr, "%s: bus: %s failed to submit cmd: %d\n", __func__, ndctl_bus_get_provider(bus), rc); ndctl_cmd_unref(cmd); return rc; } if (!tmo) { fprintf(stderr, "%s: bus: %s ars timeout\n", __func__, ndctl_bus_get_provider(bus)); return -EIO; } if (ndctl_cmd_ars_in_progress(cmd)) { tmo--; sleep(1); goto retry; } for (i = 0; i < ndctl_cmd_ars_num_records(cmd); i++) { fprintf(stderr, "%s: record[%d].addr: 0x%llx\n", __func__, i, ndctl_cmd_ars_get_record_addr(cmd, i)); fprintf(stderr, "%s: record[%d].length: 0x%llx\n", __func__, i, ndctl_cmd_ars_get_record_len(cmd, i)); } check->cmd = cmd; return 0; } static int check_clear_error(struct ndctl_bus *bus, struct check_cmd *check) { struct ndctl_cmd *ars_cap = check_cmds[ND_CMD_ARS_CAP].cmd; struct ndctl_cmd *clear_err; unsigned long long cleared; struct ndctl_range range; int rc; if (check->cmd != NULL) { fprintf(stderr, "%s: expected a NULL command, by default\n", __func__); return -ENXIO; } if (ndctl_cmd_ars_cap_get_range(ars_cap, &range)) { fprintf(stderr, "failed to get ars_cap range\n"); return -ENXIO; } fprintf(stderr, "%s: clearing at %#llx for %llu bytes\n", __func__, range.address, range.length); clear_err = ndctl_bus_cmd_new_clear_error(range.address, range.length, ars_cap); if (!clear_err) { fprintf(stderr, "%s: bus: %s failed to create cmd\n", __func__, ndctl_bus_get_provider(bus)); return -ENOTTY; } rc = ndctl_cmd_submit(clear_err); if (rc < 0) { fprintf(stderr, "%s: bus: %s failed to submit cmd: %d\n", __func__, ndctl_bus_get_provider(bus), rc); ndctl_cmd_unref(clear_err); return rc; } cleared = ndctl_cmd_clear_error_get_cleared(clear_err); if (cleared != range.length) { fprintf(stderr, "%s: bus: %s expected to clear: %lld actual: %lld\ n", __func__, ndctl_bus_get_provider(bus), range.length, cleared); return -ENXIO; } check->cmd = clear_err; return 0; } static struct ndctl_dax * get_dax_region(struct ndctl_region *region) { struct ndctl_dax *dax; ndctl_dax_foreach(region, dax) if (ndctl_dax_is_enabled(dax) && ndctl_dax_is_configured(dax)) return dax; return NULL; } static int test_daxdev_clear_error(const char *bus_name, const char *region_name) { int rc = 0, i; struct ndctl_ctx *ctx; struct ndctl_bus *bus; struct ndctl_region *region; struct ndctl_dax *dax = NULL; uint64_t base, start, offset, blocks, size; struct check_cmd __bus_cmds[] = { [ND_CMD_ARS_CAP] = {}, [ND_CMD_ARS_START] = {}, [ND_CMD_ARS_STATUS] = {}, [ND_CMD_CLEAR_ERROR] = {}, }; char path[256]; char buf[SYSFS_ATTR_SIZE]; struct log_ctx log_ctx; log_init(&log_ctx, "test/init", "NDCTL_DAXDEV_TEST"); rc = ndctl_new(&ctx); if (rc) return rc; bus = ndctl_bus_get_by_provider(ctx, NFIT_PROVIDER0); if (!bus) { rc = -ENODEV; goto cleanup; } ndctl_region_foreach(bus, region) { if (strncmp(region_name, ndctl_region_get_devname(region), 256) == 0) { /* find the dax region */ dax = get_dax_region(region); break; } } if (!dax) { rc = -ENODEV; goto cleanup; } /* get badblocks */ if (snprintf(path, 256, "/sys/devices/platform/%s/%s/%s/badblocks", NFIT_PROVIDER0, bus_name, ndctl_region_get_devname(region)) >= 256) { fprintf(stderr, "%s: buffer too small!\n", ndctl_region_get_devname(region)); rc = -ENXIO; goto cleanup; } if (__sysfs_read_attr(&log_ctx, path, buf) < 0) { rc = -ENXIO; goto cleanup; } /* retrieve badblocks from buf */ rc = sscanf(buf, "%lu %lu", &offset, &blocks); if (rc == EOF) { rc = -errno; goto cleanup; } /* get resource base */ base = ndctl_region_get_resource(region); if (base == ULLONG_MAX) { rc = -ERANGE; goto cleanup; } check_cmds = __bus_cmds; start = base + offset * 512; size = 512 * blocks; rc = check_ars_cap(bus, start, size, &check_cmds[ND_CMD_ARS_CAP]); if (rc < 0) goto cleanup; rc = check_ars_start(bus, &check_cmds[ND_CMD_ARS_START]); if (rc < 0) goto cleanup; rc = check_ars_status(bus, &check_cmds[ND_CMD_ARS_STATUS]); if (rc < 0) goto cleanup; rc = check_clear_error(bus, &check_cmds[ND_CMD_CLEAR_ERROR]); if (rc < 0) goto cleanup; for (i = 1; i < (int)ARRAY_SIZE(__bus_cmds); i++) { if (__bus_cmds[i].cmd) { ndctl_cmd_unref(__bus_cmds[i].cmd); __bus_cmds[i].cmd = NULL; } } cleanup: ndctl_unref(ctx); return rc; } int main(int argc, char *argv[]) { int rc; struct sigaction act; if (argc < 1 || argc > 4) return -EINVAL; memset(&act, 0, sizeof(act)); act.sa_sigaction = sigbus_hdl; act.sa_flags = SA_SIGINFO; if (sigaction(SIGBUS, &act, 0)) { fail(); return 1; } rc = test_daxdev_clear_error(argv[1], argv[2]); return rc; } ndctl-81/test/daxdev-errors.sh000077500000000000000000000034171476737544500165460ustar00rootroot00000000000000#!/bin/bash -x # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2015-2020 Intel Corporation. All rights reserved. set -e rc=77 . $(dirname $0)/common check_min_kver "4.12" || do_skip "lacks dax dev error handling" check_prereq "jq" trap 'err $LINENO' ERR # setup (reset nfit_test dimms) modprobe nfit_test reset rc=1 query=". | sort_by(.available_size) | reverse | .[0].dev" region=$($NDCTL list -b $NFIT_TEST_BUS0 -t pmem -Ri | jq -r "$query") json=$($NDCTL create-namespace -b $NFIT_TEST_BUS0 -r $region -t pmem -m devdax -a 4096) chardev=$(echo $json | jq ". | select(.mode == \"devdax\") | .daxregion.devices[0].chardev") #{ # "dev":"namespace6.0", # "mode":"devdax", # "size":64004096, # "uuid":"83a925dd-42b5-4ac6-8588-6a50bfc0c001", # "daxregion":{ # "id":6, # "size":64004096, # "align":4096, # "devices":[ # { # "chardev":"dax6.0", # "size":64004096 # } # ] # } #} json1=$($NDCTL list -b $NFIT_TEST_BUS0 --mode=devdax --namespaces) eval $(echo $json1 | json2var) nsdev=$dev json1=$($NDCTL list -b $NFIT_TEST_BUS0) eval $(echo $json1 | json2var) busdev=$dev # inject errors in the middle of the namespace err_sector="$(((size/512) / 2))" err_count=8 if ! read sector len < /sys/bus/nd/devices/$region/badblocks; then $NDCTL inject-error --block="$err_sector" --count=$err_count $nsdev fi read sector len < /sys/bus/nd/devices/$region/badblocks echo "sector: $sector len: $len" # run the daxdev-errors test test -x $TEST_PATH/daxdev-errors $TEST_PATH/daxdev-errors $busdev $region # check badblocks, should be empty if read sector len < /sys/bus/platform/devices/nfit_test.0/$busdev/$region/badblocks; then echo "badblocks empty, expected" fi [ -n "$sector" ] && echo "fail: $LINENO" && exit 1 check_dmesg "$LINENO" _cleanup exit 0 ndctl-81/test/device-dax-fio.sh000077500000000000000000000022161476737544500165410ustar00rootroot00000000000000#!/bin/bash # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2015-2020 Intel Corporation. All rights reserved. . $(dirname $0)/common rc=77 set -e check_min_kver "4.11" || do_skip "kernel may lack device-dax fixes" trap 'err $LINENO' ERR check_prereq "fio" if ! fio --enghelp | grep -q "dev-dax"; then echo "fio lacks dev-dax engine" exit 77 fi dev=$($TEST_PATH/dax-dev) for align in 4k 2m 1g do json=$($NDCTL create-namespace -m devdax -a $align -f -e $dev) chardev=$(echo $json | jq -r ". | select(.mode == \"devdax\") | .daxregion.devices[0].chardev") if [ align = "1g" ]; then bs="1g" else bs="2m" fi cat > fio.job <<- EOF [global] ioengine=dev-dax direct=0 filename=/dev/${chardev} verify=crc32c bs=${bs} [write] rw=write runtime=5 [read] stonewall rw=read runtime=5 EOF rc=1 fio fio.job 2>&1 | tee fio.log if grep -q "fio.*got signal" fio.log; then echo "test/device-dax-fio.sh: failed with align: $align" exit 1 fi # revert namespace to raw mode json=$($NDCTL create-namespace -m raw -f -e $dev) eval $(json2var <<< "$json") [ $mode != "fsdax" ] && echo "fail: $LINENO" && exit 1 done exit 0 ndctl-81/test/device-dax.c000066400000000000000000000251531476737544500156000ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2015-2020 Intel Corporation. All rights reserved. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static sigjmp_buf sj_env; static int create_namespace(int argc, const char **argv, void *ctx) { builtin_xaction_namespace_reset(); return cmd_create_namespace(argc, argv, ctx); } static int reset_device_dax(struct ndctl_namespace *ndns) { struct ndctl_ctx *ctx = ndctl_namespace_get_ctx(ndns); const char *argv[] = { "__func__", "-v", "-m", "raw", "-f", "-e", "", }; int argc = ARRAY_SIZE(argv); argv[argc - 1] = ndctl_namespace_get_devname(ndns); return create_namespace(argc, argv, ctx); } static int setup_device_dax(struct ndctl_namespace *ndns, unsigned long __align) { struct ndctl_ctx *ctx = ndctl_namespace_get_ctx(ndns); char align[32]; const char *argv[] = { "__func__", "-v", "-m", "devdax", "-M", "dev", "-f", "-a", align, "-e", "", }; int argc = ARRAY_SIZE(argv); argv[argc - 1] = ndctl_namespace_get_devname(ndns); sprintf(align, "%ld", __align); return create_namespace(argc, argv, ctx); } static int setup_pmem_fsdax_mode(struct ndctl_namespace *ndns, unsigned long __align) { struct ndctl_ctx *ctx = ndctl_namespace_get_ctx(ndns); char align[32]; const char *argv[] = { "__func__", "-v", "-m", "fsdax", "-M", "dev", "-f", "-a", align, "-e", "", }; int argc = ARRAY_SIZE(argv); argv[argc - 1] = ndctl_namespace_get_devname(ndns); sprintf(align, "%ld", __align); return create_namespace(argc, argv, ctx); } static void sigbus(int sig, siginfo_t *siginfo, void *d) { siglongjmp(sj_env, 1); } #define VERIFY_SIZE(x) (x * 2) #define VERIFY_BUF_SIZE 4096 /* * This timeout value derived from an Intel(R) Xeon(R) CPU E5-2690 v2 @ * 3.00GHz where the loop, for the align == 2M case, completes in 7500us * when cached and 200ms when uncached. */ #define VERIFY_TIME(x) (suseconds_t) ((ALIGN(x, SZ_2M) / SZ_4K) * 60) static int verify_data(struct daxctl_dev *dev, char *dax_buf, unsigned long align, int salt, struct ndctl_test *test) { struct timeval tv1, tv2, tv_diff; unsigned long i; if (!ndctl_test_attempt(test, KERNEL_VERSION(4, 9, 0))) return 0; /* verify data and cache mode */ gettimeofday(&tv1, NULL); for (i = 0; i < VERIFY_SIZE(align); i += VERIFY_BUF_SIZE) { unsigned int *verify = (unsigned int *) (dax_buf + i), j; for (j = 0; j < VERIFY_BUF_SIZE / sizeof(int); j++) if (verify[j] != salt + i + j) break; if (j < VERIFY_BUF_SIZE / sizeof(int)) { fprintf(stderr, "%s: @ %#lx expected %#x got %#lx\n", daxctl_dev_get_devname(dev), i, verify[j], salt + i + j); return -ENXIO; } } gettimeofday(&tv2, NULL); timersub(&tv2, &tv1, &tv_diff); tv_diff.tv_usec += tv_diff.tv_sec * 1000000; if (tv_diff.tv_usec > VERIFY_TIME(align)) { /* * Checks whether the kernel correctly mapped the * device-dax range as cacheable. */ fprintf(stderr, "%s: verify loop took too long usecs: %ld\n", daxctl_dev_get_devname(dev), tv_diff.tv_usec); return -ENXIO; } return 0; } static int test_dax_soft_offline(struct ndctl_test *test, struct ndctl_namespace *ndns) { unsigned long long resource = ndctl_namespace_get_resource(ndns); int fd, rc; char *buf; if (resource == ULLONG_MAX) { fprintf(stderr, "failed to get resource: %s\n", ndctl_namespace_get_devname(ndns)); return -ENXIO; } fd = open("/sys/devices/system/memory/soft_offline_page", O_WRONLY); if (fd < 0) { fprintf(stderr, "failed to open soft_offline_page\n"); return -ENOENT; } rc = asprintf(&buf, "%#llx\n", resource); if (rc < 0) { fprintf(stderr, "failed to alloc resource\n"); close(fd); return -ENOMEM; } fprintf(stderr, "%s: try to offline page @%#llx\n", __func__, resource); rc = write(fd, buf, rc); free(buf); close(fd); if (rc >= 0) { fprintf(stderr, "%s: should have failed\n", __func__); return -ENXIO; } return 0; } static int __test_device_dax(unsigned long align, int loglevel, struct ndctl_test *test, struct ndctl_ctx *ctx) { unsigned long i; struct sigaction act; struct ndctl_dax *dax; struct ndctl_pfn *pfn; struct daxctl_dev *dev; int fd, rc, *p, salt; struct ndctl_namespace *ndns; struct daxctl_region *dax_region; char *buf, path[100], data[VERIFY_BUF_SIZE]; ndctl_set_log_priority(ctx, loglevel); ndns = ndctl_get_test_dev(ctx); if (!ndns) { fprintf(stderr, "%s: failed to find suitable namespace\n", __func__); return 77; } if (align > SZ_2M && !ndctl_test_attempt(test, KERNEL_VERSION(4, 11, 0))) return 77; if (!ndctl_test_attempt(test, KERNEL_VERSION(4, 7, 0))) return 77; /* setup up fsdax mode pmem device and seed with verification data */ rc = setup_pmem_fsdax_mode(ndns, align); if (rc < 0 || !(pfn = ndctl_namespace_get_pfn(ndns))) { fprintf(stderr, "%s: failed device-dax setup\n", ndctl_namespace_get_devname(ndns)); goto out; } sprintf(path, "/dev/%s", ndctl_pfn_get_block_device(pfn)); fd = open(path, O_RDWR); if (fd < 0) { fprintf(stderr, "%s: failed to open pmem device\n", path); rc = -ENXIO; goto out; } srand(getpid()); salt = rand(); for (i = 0; i < VERIFY_SIZE(align); i += VERIFY_BUF_SIZE) { unsigned int *verify = (unsigned int *) data, j; for (j = 0; j < VERIFY_BUF_SIZE / sizeof(int); j++) verify[j] = salt + i + j; if (write(fd, data, sizeof(data)) != sizeof(data)) { fprintf(stderr, "%s: failed data setup\n", path); rc = -ENXIO; goto out; } } fsync(fd); close(fd); /* switch the namespace to device-dax mode and verify data via mmap */ rc = setup_device_dax(ndns, align); if (rc < 0) { fprintf(stderr, "%s: failed device-dax setup\n", ndctl_namespace_get_devname(ndns)); goto out; } dax = ndctl_namespace_get_dax(ndns); dax_region = ndctl_dax_get_daxctl_region(dax); dev = daxctl_dev_get_first(dax_region); if (!dev) { fprintf(stderr, "%s: failed to find device-dax instance\n", ndctl_namespace_get_devname(ndns)); rc = -ENXIO; goto out; } sprintf(path, "/dev/%s", daxctl_dev_get_devname(dev)); fd = open(path, O_RDONLY); if (fd < 0) { fprintf(stderr, "%s: failed to open(O_RDONLY) device-dax instance\n", daxctl_dev_get_devname(dev)); rc = -ENXIO; goto out; } buf = mmap(NULL, VERIFY_SIZE(align), PROT_READ, MAP_PRIVATE, fd, 0); if (buf != MAP_FAILED) { fprintf(stderr, "%s: expected MAP_PRIVATE failure\n", path); rc = -ENXIO; goto out; } buf = mmap(NULL, VERIFY_SIZE(align), PROT_READ, MAP_SHARED, fd, 0); if (buf == MAP_FAILED) { fprintf(stderr, "%s: expected MAP_SHARED success\n", path); return -ENXIO; } rc = verify_data(dev, buf, align, salt, test); if (rc) goto out; close(fd); munmap(buf, VERIFY_SIZE(align)); /* * Prior to 4.8-final these tests cause crashes, or are * otherwise not supported. */ if (ndctl_test_attempt(test, KERNEL_VERSION(4, 9, 0))) { static const bool devdax = false; int fd2; fd = open(path, O_RDWR); if (fd < 0) { fprintf(stderr, "%s: failed to open for direct-io test\n", daxctl_dev_get_devname(dev)); rc = -ENXIO; goto out; } rc = test_dax_directio(fd, align, NULL, 0); if (rc) { fprintf(stderr, "%s: failed dax direct-i/o\n", ndctl_namespace_get_devname(ndns)); goto out; } rc = test_dax_remap(test, fd, align, NULL, 0, devdax); if (rc) { fprintf(stderr, "%s: failed dax remap\n", ndctl_namespace_get_devname(ndns)); goto out; } close(fd); fprintf(stderr, "%s: test dax poison\n", ndctl_namespace_get_devname(ndns)); fd = open(path, O_RDWR); if (fd < 0) { fprintf(stderr, "%s: failed to open for poison test\n", daxctl_dev_get_devname(dev)); rc = -ENXIO; goto out; } rc = test_dax_soft_offline(test, ndns); if (rc) { fprintf(stderr, "%s: failed dax soft offline\n", ndctl_namespace_get_devname(ndns)); goto out; } rc = test_dax_poison(test, fd, align, NULL, 0, devdax); if (rc) { fprintf(stderr, "%s: failed dax poison\n", ndctl_namespace_get_devname(ndns)); goto out; } close(fd); fd2 = open("/proc/self/smaps", O_RDONLY); if (fd2 < 0) { fprintf(stderr, "%s: failed smaps open\n", ndctl_namespace_get_devname(ndns)); rc = -ENXIO; goto out; } do { rc = read(fd2, data, sizeof(data)); } while (rc > 0); if (rc) { fprintf(stderr, "%s: failed smaps retrieval\n", ndctl_namespace_get_devname(ndns)); rc = -ENXIO; goto out; } } /* establish a writable mapping */ fd = open(path, O_RDWR); if (fd < 0) { fprintf(stderr, "%s: failed to open(O_RDWR) device-dax instance\n", daxctl_dev_get_devname(dev)); rc = -ENXIO; goto out; } buf = mmap(NULL, VERIFY_SIZE(align), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); if (buf == MAP_FAILED) { fprintf(stderr, "%s: expected PROT_WRITE + MAP_SHARED success\n", path); return -ENXIO; } rc = reset_device_dax(ndns); if (rc < 0) { fprintf(stderr, "%s: failed to reset device-dax instance\n", ndctl_namespace_get_devname(ndns)); goto out; } memset(&act, 0, sizeof(act)); act.sa_sigaction = sigbus; act.sa_flags = SA_SIGINFO; if (sigaction(SIGBUS, &act, 0)) { perror("sigaction"); rc = EXIT_FAILURE; goto out; } /* test fault after device-dax instance disabled */ if (sigsetjmp(sj_env, 1)) { /* got sigbus, success */ close(fd); rc = 0; goto out; } rc = EXIT_SUCCESS; p = (int *) (buf + align); *p = 0xff; if (ndctl_test_attempt(test, KERNEL_VERSION(4, 9, 0))) { /* after 4.9 this test will properly get sigbus above */ rc = EXIT_FAILURE; fprintf(stderr, "%s: failed to unmap after reset\n", daxctl_dev_get_devname(dev)); } close(fd); out: reset_device_dax(ndns); return rc; } static int test_device_dax(int loglevel, struct ndctl_test *test, struct ndctl_ctx *ctx) { unsigned long i, aligns[] = { SZ_4K, SZ_2M, SZ_1G }; int rc; for (i = 0; i < ARRAY_SIZE(aligns); i++) { rc = __test_device_dax(aligns[i], loglevel, test, ctx); if (rc && rc != 77) break; } return rc; } int __attribute__((weak)) main(int argc, char *argv[]) { struct ndctl_test *test = ndctl_test_new(0); struct ndctl_ctx *ctx; int rc; if (!test) { fprintf(stderr, "failed to initialize test\n"); return EXIT_FAILURE; } rc = ndctl_new(&ctx); if (rc < 0) return ndctl_test_result(test, rc); rc = test_device_dax(LOG_DEBUG, test, ctx); ndctl_unref(ctx); return ndctl_test_result(test, rc); } ndctl-81/test/dm.sh000077500000000000000000000030601476737544500143530ustar00rootroot00000000000000#!/bin/bash -x # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2015-2020 Intel Corporation. All rights reserved. set -e SKIP=77 FAIL=1 SUCCESS=0 . $(dirname $0)/common MNT=test_dax_mnt TEST_DM_PMEM=/dev/mapper/test_pmem NAME=$(basename $TEST_DM_PMEM) mkdir -p $MNT TEST_SIZE=$((1<<30)) rc=$FAIL cleanup() { if [ $rc -ne $SUCCESS ]; then echo "test/dm.sh: failed at line $1" fi if mountpoint -q $MNT; then umount $MNT fi if [ -L $TEST_DM_PMEM ]; then dmsetup remove $TEST_DM_PMEM fi rm -rf $MNT # opportunistic cleanup, not fatal if these fail namespaces=$($NDCTL list -N | jq -r ".[] | select(.name==\"$NAME\") | .dev") for i in $namespaces do if ! $NDCTL destroy-namespace -f $i; then echo "test/sub-section.sh: cleanup() failed to destroy $i" fi done exit $rc } trap 'err $LINENO cleanup' ERR dev="x" json=$($NDCTL create-namespace -b ACPI.NFIT -s $TEST_SIZE -t pmem -m fsdax -n "$NAME") eval $(echo $json | json2var ) [ $dev = "x" ] && echo "fail: $LINENO" && exit 1 [ $mode != "fsdax" ] && echo "fail: $LINENO" && exit 1 pmem0=/dev/$blockdev size0=$((size/512)) json=$($NDCTL create-namespace -b ACPI.NFIT -s $TEST_SIZE -t pmem -m fsdax -n "$NAME") eval $(echo $json | json2var ) [ $dev = "x" ] && echo "fail: $LINENO" && exit 1 [ $mode != "fsdax" ] && echo "fail: $LINENO" && exit 1 pmem1=/dev/$blockdev size1=$((size/512)) cat < #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DIMM_PATH "/sys/devices/platform/nfit_test.0/nfit_test_dimm/test_dimm0" static int reset_bus(struct ndctl_bus *bus) { struct ndctl_region *region; struct ndctl_dimm *dimm; /* disable all regions so that set_config_data commands are permitted */ ndctl_region_foreach(bus, region) ndctl_region_disable_invalidate(region); ndctl_dimm_foreach(bus, dimm) { if (!ndctl_dimm_read_labels(dimm)) return -ENXIO; ndctl_dimm_disable(dimm); ndctl_dimm_init_labels(dimm, NDCTL_NS_VERSION_1_2); ndctl_dimm_enable(dimm); } /* set regions back to their default state */ ndctl_region_foreach(bus, region) { ndctl_region_enable(region); ndctl_region_set_align(region, sysconf(_SC_PAGESIZE) * ndctl_region_get_interleave_ways(region)); } return 0; } static int set_dimm_response(const char *dimm_path, int cmd, int error_code, struct log_ctx *log_ctx) { char path[1024], buf[SYSFS_ATTR_SIZE]; int rc; if (error_code) { sprintf(path, "%s/fail_cmd", dimm_path); sprintf(buf, "%#x\n", 1 << cmd); rc = __sysfs_write_attr(log_ctx, path, buf); if (rc) goto out; sprintf(path, "%s/fail_cmd_code", dimm_path); sprintf(buf, "%d\n", error_code); rc = __sysfs_write_attr(log_ctx, path, buf); if (rc) goto out; } else { sprintf(path, "%s/fail_cmd", dimm_path); sprintf(buf, "0\n"); rc = __sysfs_write_attr(log_ctx, path, buf); if (rc) goto out; } out: if (rc < 0) fprintf(stderr, "%s failed, cmd: %d code: %d\n", __func__, cmd, error_code); return 0; } static int dimms_disable(struct ndctl_bus *bus) { struct ndctl_dimm *dimm; ndctl_dimm_foreach(bus, dimm) { int rc = ndctl_dimm_disable(dimm); if (rc) { fprintf(stderr, "dimm: %s failed to disable: %d\n", ndctl_dimm_get_devname(dimm), rc); return rc; } } return 0; } static int test_dimms_enable(struct ndctl_bus *bus, struct ndctl_dimm *victim, bool expect) { struct ndctl_dimm *dimm; ndctl_dimm_foreach(bus, dimm) { int rc = ndctl_dimm_enable(dimm); if (((expect != (rc == 0)) && (dimm == victim)) || (rc && dimm != victim)) { bool __expect = true; if (dimm == victim) __expect = expect; fprintf(stderr, "fail expected %s enable %s victim: %s rc: %d\n", ndctl_dimm_get_devname(dimm), __expect ? "success" : "failure", ndctl_dimm_get_devname(victim), rc); return -ENXIO; } } return 0; } static int test_regions_enable(struct ndctl_bus *bus, struct ndctl_dimm *victim, struct ndctl_region *victim_region, bool region_expect, int namespace_count) { struct ndctl_region *region; ndctl_region_foreach(bus, region) { struct ndctl_namespace *ndns; struct ndctl_dimm *dimm; bool has_victim = false; int rc, count = 0; ndctl_dimm_foreach_in_region(region, dimm) { if (dimm == victim) { has_victim = true; break; } } rc = ndctl_region_enable(region); fprintf(stderr, "region: %s enable: %d has_victim: %d\n", ndctl_region_get_devname(region), rc, has_victim); if (((region_expect != (rc == 0)) && has_victim) || (rc && !has_victim)) { bool __expect = true; if (has_victim) __expect = region_expect; fprintf(stderr, "%s: fail expected enable: %s with %s\n", ndctl_region_get_devname(region), __expect ? "success" : "failure", ndctl_dimm_get_devname(victim)); return -ENXIO; } if (region != victim_region) continue; ndctl_namespace_foreach(region, ndns) { if (ndctl_namespace_is_enabled(ndns)) { fprintf(stderr, "%s: enabled, expected disabled\n", ndctl_namespace_get_devname(ndns)); return -ENXIO; } fprintf(stderr, "%s: %s: size: %lld\n", __func__, ndctl_namespace_get_devname(ndns), ndctl_namespace_get_size(ndns)); count++; } if (count != namespace_count) { fprintf(stderr, "%s: fail expected %d namespaces got %d\n", ndctl_region_get_devname(region), namespace_count, count); return -ENXIO; } } return 0; } static int do_test(struct ndctl_ctx *ctx, struct ndctl_test *test) { struct ndctl_bus *bus = ndctl_bus_get_by_provider(ctx, "nfit_test.0"); struct ndctl_region *region, *victim_region = NULL; struct ndctl_dimm *dimm, *victim = NULL; char path[1024], buf[SYSFS_ATTR_SIZE]; struct log_ctx log_ctx; unsigned int handle; int rc, err = 0; if (!ndctl_test_attempt(test, KERNEL_VERSION(4, 9, 0))) return 77; if (!bus) return -ENXIO; log_init(&log_ctx, "test/dsm-fail", "NDCTL_TEST"); if (reset_bus(bus)) { fprintf(stderr, "failed to read labels\n"); return -ENXIO; } sprintf(path, "%s/handle", DIMM_PATH); rc = __sysfs_read_attr(&log_ctx, path, buf); if (rc) { fprintf(stderr, "failed to retrieve test dimm handle\n"); return -ENXIO; } handle = strtoul(buf, NULL, 0); ndctl_dimm_foreach(bus, dimm) if (ndctl_dimm_get_handle(dimm) == handle) { victim = dimm; break; } if (!victim) { fprintf(stderr, "failed to find victim dimm\n"); return -ENXIO; } fprintf(stderr, "victim: %s\n", ndctl_dimm_get_devname(victim)); ndctl_region_foreach(bus, region) { if (ndctl_region_get_type(region) != ND_DEVICE_REGION_PMEM) continue; ndctl_dimm_foreach_in_region(region, dimm) { const char *argv[] = { "__func__", "-v", "-r", ndctl_region_get_devname(region), "-s", "4M", "-m", "raw", }; struct ndctl_namespace *ndns; int count, i; if (dimm != victim) continue; /* * Validate that we only have the one seed * namespace, and then create one so that we can * verify namespace enumeration while locked. */ count = 0; ndctl_namespace_foreach(region, ndns) count++; if (count != 1) { fprintf(stderr, "%s: found %d namespaces expected 1\n", ndctl_region_get_devname(region), count); rc = -ENXIO; goto out; } if (ndctl_region_get_size(region) != ndctl_region_get_available_size(region)) { fprintf(stderr, "%s: expected empty region\n", ndctl_region_get_devname(region)); rc = -ENXIO; goto out; } for (i = 0; i < 2; i++) { builtin_xaction_namespace_reset(); rc = cmd_create_namespace(ARRAY_SIZE(argv), argv, ndctl_region_get_ctx(region)); if (rc) { fprintf(stderr, "%s: failed to create namespace\n", ndctl_region_get_devname(region)); rc = -ENXIO; goto out; } } victim_region = region; } if (victim_region) break; } /* disable all regions so that we can disable a dimm */ ndctl_region_foreach(bus, region) ndctl_region_disable_invalidate(region); rc = dimms_disable(bus); if (rc) goto out; rc = set_dimm_response(DIMM_PATH, ND_CMD_GET_CONFIG_SIZE, -EACCES, &log_ctx); if (rc) goto out; rc = test_dimms_enable(bus, victim, true); if (rc) goto out; rc = test_regions_enable(bus, victim, victim_region, true, 2); if (rc) goto out; rc = set_dimm_response(DIMM_PATH, ND_CMD_GET_CONFIG_SIZE, 0, &log_ctx); if (rc) goto out; ndctl_region_foreach(bus, region) ndctl_region_disable_invalidate(region); rc = dimms_disable(bus); if (rc) goto out; rc = set_dimm_response(DIMM_PATH, ND_CMD_GET_CONFIG_DATA, -EACCES, &log_ctx); if (rc) goto out; rc = test_dimms_enable(bus, victim, false); if (rc) goto out; rc = test_regions_enable(bus, victim, victim_region, false, 0); if (rc) goto out; rc = set_dimm_response(DIMM_PATH, ND_CMD_GET_CONFIG_DATA, 0, &log_ctx); if (rc) goto out; rc = dimms_disable(bus); if (rc) goto out; out: err = rc; sprintf(path, "%s/fail_cmd", DIMM_PATH); sprintf(buf, "0\n"); rc = __sysfs_write_attr(&log_ctx, path, buf); if (rc) fprintf(stderr, "%s: failed to clear fail_cmd mask\n", ndctl_dimm_get_devname(victim)); rc = ndctl_dimm_enable(victim); if (rc) { fprintf(stderr, "failed to enable victim: %s after clearing error\n", ndctl_dimm_get_devname(victim)); rc = -ENXIO; } reset_bus(bus); if (rc) err = rc; return err; } int test_dsm_fail(int loglevel, struct ndctl_test *test, struct ndctl_ctx *ctx) { struct kmod_module *mod; struct kmod_ctx *kmod_ctx; int result = EXIT_FAILURE, err; ndctl_set_log_priority(ctx, loglevel); err = ndctl_test_init(&kmod_ctx, &mod, NULL, loglevel, test); if (err < 0) { result = 77; ndctl_test_skip(test); fprintf(stderr, "%s unavailable skipping tests\n", "nfit_test"); return result; } result = do_test(ctx, test); kmod_module_remove_module(mod, 0); kmod_unref(kmod_ctx); return result; } int __attribute__((weak)) main(int argc, char *argv[]) { struct ndctl_test *test = ndctl_test_new(0); struct ndctl_ctx *ctx; int rc; if (!test) { fprintf(stderr, "failed to initialize test\n"); return EXIT_FAILURE; } rc = ndctl_new(&ctx); if (rc) return ndctl_test_result(test, rc); rc = test_dsm_fail(LOG_DEBUG, test, ctx); ndctl_unref(ctx); return ndctl_test_result(test, rc); } ndctl-81/test/firmware-update.sh000077500000000000000000000050621476737544500170530ustar00rootroot00000000000000#!/bin/bash -Ex # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2018-2020 Intel Corporation. All rights reserved. rc=77 dev="" image="update-fw.img" . $(dirname $0)/common trap 'err $LINENO' ERR fwupd_reset() { reset if [ -f $image ]; then rm -f $image fi } detect() { $NDCTL wait-scrub $NFIT_TEST_BUS0 fwa=$($NDCTL list -b $NFIT_TEST_BUS0 -F | jq -r '.[0].firmware.activate_method') [ $fwa = "suspend" ] || err "$LINENO" count=$($NDCTL list -b $NFIT_TEST_BUS0 -D | jq length) [ $((count)) -eq 4 ] || err "$LINENO" } do_tests() { # create a dummy image file, try to update all 4 dimms on # nfit_test.0, validate that all get staged, validate that all # but one get armed relative to an overflow error. truncate -s 196608 $image json=$($NDCTL update-firmware -b $NFIT_TEST_BUS0 -f $image all) count=$(jq 'map(select(.firmware.activate_state == "armed")) | length' <<< $json) [ $((count)) -eq 3 ] || err "$LINENO" count=$(jq 'map(select(.firmware.activate_state == "idle")) | length' <<< $json) [ $((count)) -eq 1 ] || err "$LINENO" # validate that the overflow dimm can be force armed dev=$(jq -r '.[] | select(.firmware.activate_state == "idle").dev' <<< $json) json=$($NDCTL update-firmware -b $NFIT_TEST_BUS0 $dev -A --force) state=$(jq -r '.[0].firmware.activate_state' <<< $json) [ $state = "armed" ] || err "$LINENO" # validate that the bus indicates overflow fwa=$($NDCTL list -b $NFIT_TEST_BUS0 -F | jq -r '.[0].firmware.activate_state') [ $fwa = "overflow" ] || err "$LINENO" # validate that all devices can be disarmed, and the bus goes idle json=$($NDCTL update-firmware -b $NFIT_TEST_BUS0 -D all) count=$(jq 'map(select(.firmware.activate_state == "idle")) | length' <<< $json) [ $((count)) -eq 4 ] || err "$LINENO" fwa=$($NDCTL list -b $NFIT_TEST_BUS0 -F | jq -r '.[0].firmware.activate_state') [ $fwa = "idle" ] || err "$LINENO" # re-arm all DIMMs json=$($NDCTL update-firmware -b $NFIT_TEST_BUS0 -A --force all) count=$(jq 'map(select(.firmware.activate_state == "armed")) | length' <<< $json) [ $((count)) -eq 4 ] || err "$LINENO" # trigger activation via suspend json=$($NDCTL activate-firmware -v $NFIT_TEST_BUS0) idle_count=$(jq '.[].dimms | map(select(.firmware.activate_state == "idle")) | length' <<< $json) busy_count=$(jq '.[].dimms | map(select(.firmware.activate_state == "busy")) | length' <<< $json) [ $((idle_count)) -eq 4 -o $((busy_count)) -eq 4 ] || err "$LINENO" } check_min_kver "4.16" || do_skip "may lack firmware update test handling" modprobe nfit_test fwupd_reset detect rc=1 do_tests rm -f $image _cleanup exit 0 ndctl-81/test/hugetlb.c000066400000000000000000000011401476737544500152070ustar00rootroot00000000000000#include #include #include #include #include static int test_hugetlb(void) { int rc, i; unsigned long aligns[] = { SZ_4K, SZ_2M, SZ_1G }; for (i = 0; i < (int) ARRAY_SIZE(aligns); i++) { fprintf(stderr, "%s: page_size: %#lx\n", __func__, aligns[i]); rc = test_dax_directio(-1, aligns[i], NULL, 0); if (rc == -ENOENT && aligns[i] == SZ_1G) continue; /* system not configured for 1G pages */ else if (rc) return 77; } return 0; } int __attribute__((weak)) main(int argc, char *argv[]) { return test_hugetlb(); } ndctl-81/test/inject-error.sh000077500000000000000000000040271476737544500163620ustar00rootroot00000000000000#!/bin/bash -Ex # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2015-2020 Intel Corporation. All rights reserved. dev="" size="" blockdev="" rc=77 err_block=42 err_count=8 . $(dirname $0)/common check_prereq "jq" trap 'err $LINENO' ERR # sample json: #{ # "dev":"namespace7.0", # "mode":"fsdax", # "size":"60.00 MiB (62.92 MB)", # "uuid":"f1baa71a-d165-4da4-bb6a-083a2b0e6469", # "blockdev":"pmem7", #} check_min_kver "4.15" || do_skip "kernel $KVER may not support error injection" create() { json=$($NDCTL create-namespace -b $NFIT_TEST_BUS0 -t pmem --align=4k) rc=2 eval "$(echo "$json" | json2var)" [ -n "$dev" ] || err "$LINENO" [ -n "$size" ] || err "$LINENO" [ -n "$blockdev" ] || err "$LINENO" [ $size -gt 0 ] || err "$LINENO" } check_status() { local sector="$1" local count="$2" json="$($NDCTL inject-error --status $dev)" [[ "$sector" == "$(jq ".badblocks[0].block" <<< "$json")" ]] [[ "$count" == "$(jq ".badblocks[0].count" <<< "$json")" ]] } do_tests() { # inject without notification $NDCTL inject-error --block=$err_block --count=$err_count --no-notify $dev check_status "$err_block" "$err_count" if read -r sector len < /sys/block/$blockdev/badblocks; then # fail if reading badblocks returns data echo "fail: $LINENO" && exit 1 fi # clear via err-inj-clear $NDCTL inject-error --block=$err_block --count=$err_count --uninject $dev check_status # inject normally $NDCTL inject-error --block=$err_block --count=$err_count $dev $NDCTL start-scrub $NFIT_TEST_BUS0 && $NDCTL wait-scrub $NFIT_TEST_BUS0 check_status "$err_block" "$err_count" if read -r sector len < /sys/block/$blockdev/badblocks; then test "$sector" -eq "$err_block" test "$len" -eq "$err_count" fi # clear via write dd if=/dev/zero of=/dev/$blockdev bs=512 count=$err_count seek=$err_block oflag=direct if read -r sector len < /sys/block/$blockdev/badblocks; then # fail if reading badblocks returns data echo "fail: $LINENO" && exit 1 fi check_status } modprobe nfit_test rc=1 reset && create do_tests reset _cleanup exit 0 ndctl-81/test/inject-smart.sh000077500000000000000000000057211476737544500163610ustar00rootroot00000000000000#!/bin/bash -Ex # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2018-2020 Intel Corporation. All rights reserved. rc=77 . $(dirname $0)/common bus="$NFIT_TEST_BUS0" inj_val="42" trap 'err $LINENO' ERR # sample json: # { # "dev":"nmem0", # "id":"cdab-0a-07e0-ffffffff", # "handle":0, # "phys_id":0, # "health":{ # "health_state":"non-critical", # "temperature_celsius":23, # "spares_percentage":75, # "alarm_temperature":true, # "alarm_spares":true, # "temperature_threshold":40, # "spares_threshold":5, # "life_used_percentage":5, # "shutdown_state":"clean" # } #} translate_field() { local in="$1" case $in in media-temperature) echo "temperature_celsius" ;; ctrl-temperature) echo "controller_temperature_celsius" ;; spares) echo "spares_percentage" ;; media-temperature-alarm) echo "alarm_temperature" ;; ctrl-temperature-alarm) echo "alarm_controller_temperature" ;; spares-alarm) echo "alarm_spares" ;; media-temperature-threshold) echo "temperature_threshold" ;; spares-threshold) echo "spares_threshold" ;; unsafe-shutdown) echo "shutdown_state" ;; fatal) echo "health_state" ;; *) # passthrough echo "$in" return ;; esac } translate_val() { local in="$1" case $in in dirty) ;& fatal) ;& true) echo "1" ;; non-critical) ;& clean) ;& false) echo "0" ;; *) # passthrough echo "$in" ;; esac } get_field() { local field="$1" local smart_listing="$(translate_field $field)" json="$($NDCTL list -b $bus -d $dimm -H)" val="$(jq -r ".[].dimms[].health.$smart_listing" <<< $json)" val="$(translate_val $val)" printf "%0.0f\n" "$val" } verify() { local field="$1" local val="$(printf "%0.0f\n" "$2")" [[ "$val" == "$(get_field $field)" ]] } test_field() { local field="$1" local val="$2" local op="$3" local old_val="" if [ -n "$val" ]; then inj_opt="--${field}=${val}" else inj_opt="--${field}" fi old_val=$(get_field $field) if [[ "$old_val" == "0" || "$old_val" == "1" ]]; then val=$(((old_val + 1) % 2)) fi $NDCTL inject-smart -b $bus $dimm $inj_opt verify $field $val if [[ "$op" != "thresh" ]]; then $NDCTL inject-smart -b $bus --${field}-uninject $dimm verify $field $old_val fi } do_tests() { local fields_val=(media-temperature spares) local fields_bool=(unsafe-shutdown fatal) local fields_thresh=(media-temperature-threshold spares-threshold) local field="" $NDCTL inject-smart -b $bus --uninject-all $dimm # start tests for field in "${fields_val[@]}"; do test_field $field $inj_val done for field in "${fields_bool[@]}"; do test_field $field done for field in "${fields_thresh[@]}"; do test_field $field $inj_val "thresh" done } check_min_kver "4.19" || do_skip "kernel $KVER may not support smart (un)injection" check_prereq "jq" modprobe nfit_test rc=1 jlist=$($TEST_PATH/list-smart-dimm -b $bus) dimm="$(jq '.[]."dev"?, ."dev"?' <<< $jlist | sort | head -1 | xargs)" test -n "$dimm" do_tests _cleanup exit 0 ndctl-81/test/label-compat.sh000077500000000000000000000020621476737544500163140ustar00rootroot00000000000000#!/bin/bash -x # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2015-2020 Intel Corporation. All rights reserved. set -e rc=77 BASE=$(dirname $0) . $BASE/common check_min_kver "4.11" || do_skip "may not provide reliable isetcookie values" check_prereq "jq" trap 'err $LINENO' ERR # setup (reset nfit_test dimms) modprobe nfit_test $NDCTL disable-region -b $NFIT_TEST_BUS0 all $NDCTL init-labels -f -b $NFIT_TEST_BUS0 all # grab the largest pmem region on -b $NFIT_TEST_BUS0 query=". | sort_by(.available_size) | reverse | .[0].dev" region=$($NDCTL list -b $NFIT_TEST_BUS0 -t pmem -Ri | jq -r "$query") # we assume that $region is comprised of 4 dimms query=". | .regions[0].mappings | sort_by(.dimm) | .[].dimm" dimms=$($NDCTL list -DRi -r $region | jq -r "$query" | xargs) i=1 for d in $dimms do $NDCTL write-labels $d -i $BASE/nmem${i}.bin i=$((i+1)) done $NDCTL enable-region -b $NFIT_TEST_BUS0 all len=$($NDCTL list -r $region -N | jq -r "length") if [ -z $len ]; then rc=1 echo "failed to find legacy isetcookie namespace" exit 1 fi _cleanup exit 0 ndctl-81/test/libndctl.c000066400000000000000000002060531476737544500153620ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-2.1 // Copyright (C) 2014-2020, Intel Corporation. All rights reserved. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define BLKROGET _IO(0x12,94) /* get read-only status (0 = read_write) */ #define BLKROSET _IO(0x12,93) /* set device read-only (0 = read-write) */ /* * Kernel provider "nfit_test.0" produces an NFIT with the following attributes: * * (a) (b) DIMM * +-------------------+--------+--------+--------+ * +------+ | pm0.0 | free | pm1.0 | free | 0 * | imc0 +--+- - - region0- - - +--------+ +--------+ * +--+---+ | pm0.0 | free | pm1.0 | free | 1 * | +-------------------+--------v v--------+ * +--+---+ | | * | cpu0 | region1 * +--+---+ | | * | +----------------------------^ ^--------+ * +--+---+ | free | pm1.0 | free | 2 * | imc1 +--+----------------------------| +--------+ * +------+ | free | pm1.0 | free | 3 * +----------------------------+--------+--------+ * * In this platform we have four DIMMs and two memory controllers in one * socket. Each PMEM interleave set is identified by a region device with * a dynamically assigned id. * * 1. The first portion of DIMM0 and DIMM1 are interleaved as REGION0. A * single PMEM namespace is created in the REGION0-SPA-range that spans most * of DIMM0 and DIMM1 with a user-specified name of "pm0.0". Some of that * interleaved system-physical-address range is left free for * another PMEM namespace to be defined. * * 2. In the last portion of DIMM0 and DIMM1 we have an interleaved * system-physical-address range, REGION1, that spans those two DIMMs as * well as DIMM2 and DIMM3. Some of REGION1 is allocated to a PMEM namespace * named "pm1.0". * * Kernel provider "nfit_test.1" produces an NFIT with the following attributes: * * region2 * +---------------------+ * |---------------------| * || pm2.0 || * |---------------------| * +---------------------+ * * *) Describes a simple system-physical-address range with a non-aliasing backing * dimm. */ static const char *NFIT_PROVIDER0 = "nfit_test.0"; static const char *NFIT_PROVIDER1 = "nfit_test.1"; #define SZ_4K 0x00001000 #define SZ_128K 0x00020000 #define SZ_7M 0x00700000 #define SZ_2M 0x00200000 #define SZ_8M 0x00800000 #define SZ_11M 0x00b00000 #define SZ_12M 0x00c00000 #define SZ_16M 0x01000000 #define SZ_18M 0x01200000 #define SZ_20M 0x01400000 #define SZ_27M 0x01b00000 #define SZ_28M 0x01c00000 #define SZ_32M 0x02000000 #define SZ_64M 0x04000000 #define SZ_1G 0x40000000 struct dimm { unsigned int handle; unsigned int phys_id; unsigned int subsystem_vendor; unsigned short manufacturing_date; unsigned char manufacturing_location; long long dirty_shutdown; union { unsigned long flags; struct { unsigned int f_arm:1; unsigned int f_save:1; unsigned int f_flush:1; unsigned int f_smart:1; unsigned int f_restore:1; }; }; int formats; int format[2]; }; #define DIMM_HANDLE(n, s, i, c, d) \ (((n & 0xfff) << 16) | ((s & 0xf) << 12) | ((i & 0xf) << 8) \ | ((c & 0xf) << 4) | (d & 0xf)) static struct dimm dimms0[] = { { DIMM_HANDLE(0, 0, 0, 0, 0), 0, 0, 2016, 10, 42, { 0 }, 1, { 0x201, }, }, { DIMM_HANDLE(0, 0, 0, 0, 1), 1, 0, 2016, 10, 42, { 0 }, 1, { 0x201, }, }, { DIMM_HANDLE(0, 0, 1, 0, 0), 2, 0, 2016, 10, 42, { 0 }, 1, { 0x201, }, }, { DIMM_HANDLE(0, 0, 1, 0, 1), 3, 0, 2016, 10, 42, { 0 }, 1, { 0x201, }, }, }; static struct dimm dimms1[] = { { DIMM_HANDLE(0, 0, 0, 0, 0), 0, 0, 2016, 10, 42, { .f_arm = 1, .f_save = 1, .f_flush = 1, .f_smart = 1, .f_restore = 1, }, 1, { 0x101, }, }, }; static struct btt { int enabled; uuid_t uuid; int num_sector_sizes; unsigned int sector_sizes[7]; } default_btt = { 0, { 0, }, 7, { 512, 520, 528, 4096, 4104, 4160, 4224, }, }; struct pfn { int enabled; uuid_t uuid; enum ndctl_pfn_loc locs[2]; unsigned long aligns[4]; }; struct dax { int enabled; uuid_t uuid; enum ndctl_pfn_loc locs[2]; unsigned long aligns[4]; }; static struct pfn_default { int enabled; uuid_t uuid; enum ndctl_pfn_loc loc; unsigned long align; } default_pfn = { .enabled = 0, .uuid = { 0, }, .loc = NDCTL_PFN_LOC_NONE, .align = SZ_2M, }; struct region { union { unsigned int range_index; unsigned int handle; }; unsigned int interleave_ways; int enabled; char *type; unsigned long long available_size; unsigned long long size; struct set { int active; } iset; struct btt *btts[2]; struct pfn_default *pfns[2]; struct namespace *namespaces[4]; }; static struct btt btt_settings = { .enabled = 1, .uuid = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, .num_sector_sizes = 7, .sector_sizes = { 512, 520, 528, 4096, 4104, 4160, 4224, }, }; static struct pfn pfn_settings = { .enabled = 1, .uuid = { 1, 2, 3, 4, 5, 6, 7, 0, 8, 9, 10, 11, 12, 13, 14, 15 }, .locs = { NDCTL_PFN_LOC_RAM, NDCTL_PFN_LOC_PMEM }, }; static struct dax dax_settings = { .enabled = 1, .uuid = { 1, 2, 3, 4, 5, 6, 7, 0, 8, 9, 10, 11, 12, 13, 14, 15 }, .locs = { NDCTL_PFN_LOC_RAM, NDCTL_PFN_LOC_PMEM }, }; struct namespace { unsigned int id; char *type; struct btt *btt_settings; struct pfn *pfn_settings; struct dax *dax_settings; unsigned long long size; uuid_t uuid; int do_configure; int check_alt_name; int ro; int num_sector_sizes; unsigned long *sector_sizes; }; static uuid_t null_uuid; static unsigned long pmem_sector_sizes[] = { 512, 4096 }; static unsigned long io_sector_sizes[] = { 0 }; static struct namespace namespace0_pmem0 = { 0, "namespace_pmem", &btt_settings, &pfn_settings, &dax_settings, SZ_18M, { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, }, 1, 1, 0, ARRAY_SIZE(pmem_sector_sizes), pmem_sector_sizes, }; static struct namespace namespace1_pmem0 = { 0, "namespace_pmem", &btt_settings, &pfn_settings, &dax_settings, SZ_20M, { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, }, 1, 1, 0, ARRAY_SIZE(pmem_sector_sizes), pmem_sector_sizes, }; static struct region regions0[] = { { { 1 }, 2, 1, "pmem", SZ_32M, SZ_32M, { 1 }, .namespaces = { [0] = &namespace0_pmem0, }, .btts = { [0] = &default_btt, }, .pfns = { [0] = &default_pfn, }, }, { { 2 }, 4, 1, "pmem", SZ_64M, SZ_64M, { 1 }, .namespaces = { [0] = &namespace1_pmem0, }, .btts = { [0] = &default_btt, }, .pfns = { [0] = &default_pfn, }, }, }; static struct namespace namespace1 = { 0, "namespace_io", &btt_settings, &pfn_settings, &dax_settings, SZ_32M, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }, -1, 0, 1, ARRAY_SIZE(io_sector_sizes), io_sector_sizes, }; static struct region regions1[] = { { { 1 }, 1, 1, "pmem", 0, SZ_32M, .namespaces = { [0] = &namespace1, }, }, }; static unsigned long dimm_commands0 = 1UL << ND_CMD_GET_CONFIG_SIZE | 1UL << ND_CMD_GET_CONFIG_DATA | 1UL << ND_CMD_SET_CONFIG_DATA | 1UL << ND_CMD_SMART | 1UL << ND_CMD_SMART_THRESHOLD; #define CLEAR_ERROR_CMDS (1UL << ND_CMD_CLEAR_ERROR) #define ARS_CMDS (1UL << ND_CMD_ARS_CAP | 1UL << ND_CMD_ARS_START \ | 1UL << ND_CMD_ARS_STATUS) static unsigned long bus_commands0 = CLEAR_ERROR_CMDS | ARS_CMDS; static struct ndctl_dimm *get_dimm_by_handle(struct ndctl_bus *bus, unsigned int handle) { struct ndctl_dimm *dimm; ndctl_dimm_foreach(bus, dimm) if (ndctl_dimm_get_handle(dimm) == handle) return dimm; return NULL; } static struct ndctl_btt *get_idle_btt(struct ndctl_region *region) { struct ndctl_btt *btt; ndctl_btt_foreach(region, btt) if (!ndctl_btt_is_enabled(btt) && !ndctl_btt_is_configured(btt)) return btt; return NULL; } static struct ndctl_pfn *get_idle_pfn(struct ndctl_region *region) { struct ndctl_pfn *pfn; ndctl_pfn_foreach(region, pfn) if (!ndctl_pfn_is_enabled(pfn) && !ndctl_pfn_is_configured(pfn)) return pfn; return NULL; } static struct ndctl_dax *get_idle_dax(struct ndctl_region *region) { struct ndctl_dax *dax; ndctl_dax_foreach(region, dax) if (!ndctl_dax_is_enabled(dax) && !ndctl_dax_is_configured(dax)) return dax; return NULL; } static struct ndctl_namespace *get_namespace_by_id(struct ndctl_region *region, struct namespace *namespace) { struct ndctl_namespace *ndns; if (memcmp(namespace->uuid, null_uuid, sizeof(uuid_t)) != 0) ndctl_namespace_foreach(region, ndns) { uuid_t ndns_uuid; int cmp; ndctl_namespace_get_uuid(ndns, ndns_uuid); cmp = memcmp(ndns_uuid, namespace->uuid, sizeof(uuid_t)); if (cmp == 0) return ndns; } /* fall back to nominal id if uuid is not configured yet */ ndctl_namespace_foreach(region, ndns) if (ndctl_namespace_get_id(ndns) == namespace->id) return ndns; return NULL; } static struct ndctl_region *get_pmem_region_by_range_index(struct ndctl_bus *bus, unsigned int range_index) { struct ndctl_region *region; ndctl_region_foreach(bus, region) { if (ndctl_region_get_type(region) != ND_DEVICE_REGION_PMEM) continue; if (ndctl_region_get_range_index(region) == range_index) return region; } return NULL; } enum ns_mode { BTT, PFN, DAX, }; static int check_namespaces(struct ndctl_region *region, struct namespace **namespaces, enum ns_mode mode); static int check_btts(struct ndctl_region *region, struct btt **btts); static int check_regions(struct ndctl_bus *bus, struct region *regions, int n, enum ns_mode mode) { struct ndctl_region *region; int i, rc = 0; for (i = 0; i < n; i++) { struct ndctl_interleave_set *iset; char devname[50]; region = get_pmem_region_by_range_index(bus, regions[i].range_index); if (!region) { fprintf(stderr, "failed to find region type: %s ident: %x\n", regions[i].type, regions[i].handle); return -ENXIO; } snprintf(devname, sizeof(devname), "region%d", ndctl_region_get_id(region)); if (strcmp(ndctl_region_get_type_name(region), regions[i].type) != 0) { fprintf(stderr, "%s: expected type: %s got: %s\n", devname, regions[i].type, ndctl_region_get_type_name(region)); return -ENXIO; } if (ndctl_region_get_interleave_ways(region) != regions[i].interleave_ways) { fprintf(stderr, "%s: expected interleave_ways: %d got: %d\n", devname, regions[i].interleave_ways, ndctl_region_get_interleave_ways(region)); return -ENXIO; } if (regions[i].enabled && !ndctl_region_is_enabled(region)) { fprintf(stderr, "%s: expected enabled by default\n", devname); return -ENXIO; } if (regions[i].available_size != ndctl_region_get_available_size(region)) { fprintf(stderr, "%s: expected available_size: %#llx got: %#llx\n", devname, regions[i].available_size, ndctl_region_get_available_size(region)); return -ENXIO; } if (regions[i].size != ndctl_region_get_size(region)) { fprintf(stderr, "%s: expected size: %#llx got: %#llx\n", devname, regions[i].size, ndctl_region_get_size(region)); return -ENXIO; } iset = ndctl_region_get_interleave_set(region); if (regions[i].iset.active && !(iset && ndctl_interleave_set_is_active(iset) > 0)) { fprintf(stderr, "%s: expected interleave set active by default\n", devname); return -ENXIO; } else if (regions[i].iset.active == 0 && iset) { fprintf(stderr, "%s: expected no interleave set\n", devname); return -ENXIO; } if (ndctl_region_disable_invalidate(region) < 0) { fprintf(stderr, "%s: failed to disable\n", devname); return -ENXIO; } if (regions[i].enabled && ndctl_region_enable(region) < 0) { fprintf(stderr, "%s: failed to enable\n", devname); return -ENXIO; } rc = check_btts(region, regions[i].btts); if (rc) return rc; if (regions[i].namespaces[0]) rc = check_namespaces(region, regions[i].namespaces, mode); if (rc) break; } if (rc == 0) ndctl_region_foreach(bus, region) ndctl_region_disable_invalidate(region); return rc; } static int validate_dax(struct ndctl_dax *dax) { /* TODO: make nfit_test namespaces dax capable */ struct ndctl_namespace *ndns = ndctl_dax_get_namespace(dax); const char *devname = ndctl_namespace_get_devname(ndns); struct ndctl_region *region = ndctl_dax_get_region(dax); struct ndctl_ctx *ctx = ndctl_dax_get_ctx(dax); struct ndctl_test *test = ndctl_get_private_data(ctx); struct daxctl_region *dax_region = NULL, *found; int rc = -ENXIO, fd, count, dax_expect; struct daxctl_dev *dax_dev, *seed; struct daxctl_ctx *dax_ctx; uuid_t uuid, region_uuid; char devpath[50]; dax_region = ndctl_dax_get_daxctl_region(dax); if (!dax_region) { fprintf(stderr, "%s: failed to retrieve daxctl_region\n", devname); return -ENXIO; } dax_ctx = ndctl_get_daxctl_ctx(ctx); count = 0; daxctl_region_foreach(dax_ctx, found) if (found == dax_region) count++; if (count != 1) { fprintf(stderr, "%s: failed to iterate to single region instance\n", devname); return -ENXIO; } if (ndctl_test_attempt(test, KERNEL_VERSION(4, 10, 0))) { if (daxctl_region_get_size(dax_region) != ndctl_dax_get_size(dax)) { fprintf(stderr, "%s: expect size: %llu != %llu\n", devname, ndctl_dax_get_size(dax), daxctl_region_get_size(dax_region)); return -ENXIO; } if (daxctl_region_get_align(dax_region) != ndctl_dax_get_align(dax)) { fprintf(stderr, "%s: expect align: %lu != %lu\n", devname, ndctl_dax_get_align(dax), daxctl_region_get_align(dax_region)); return -ENXIO; } } rc = -ENXIO; ndctl_dax_get_uuid(dax, uuid); daxctl_region_get_uuid(dax_region, region_uuid); if (uuid_compare(uuid, region_uuid) != 0) { char expect[40], actual[40]; uuid_unparse(region_uuid, actual); uuid_unparse(uuid, expect); fprintf(stderr, "%s: expected uuid: %s got: %s\n", devname, expect, actual); goto out; } if ((int) ndctl_region_get_id(region) != daxctl_region_get_id(dax_region)) { fprintf(stderr, "%s: expected region id: %d got: %d\n", devname, ndctl_region_get_id(region), daxctl_region_get_id(dax_region)); goto out; } dax_dev = daxctl_dev_get_first(dax_region); if (!dax_dev) { fprintf(stderr, "%s: failed to find daxctl_dev\n", devname); goto out; } seed = daxctl_region_get_dev_seed(dax_region); if (dax_dev != seed && daxctl_dev_get_size(dax_dev) <= 0) { fprintf(stderr, "%s: expected non-zero sized dax device\n", devname); goto out; } sprintf(devpath, "/dev/%s", daxctl_dev_get_devname(dax_dev)); fd = open(devpath, O_RDWR); if (fd < 0) { fprintf(stderr, "%s: failed to open %s\n", devname, devpath); goto out; } close(fd); count = 0; daxctl_dev_foreach(dax_region, dax_dev) count++; dax_expect = seed ? 2 : 1; if (count != dax_expect) { fprintf(stderr, "%s: expected %d dax device%s, got %d\n", devname, dax_expect, dax_expect == 1 ? "" : "s", count); rc = -ENXIO; goto out; } rc = 0; out: daxctl_region_unref(dax_region); return rc; } static int __check_dax_create(struct ndctl_region *region, struct ndctl_namespace *ndns, struct namespace *namespace, enum ndctl_pfn_loc loc, uuid_t uuid) { struct ndctl_dax *dax_seed = ndctl_region_get_dax_seed(region); struct ndctl_ctx *ctx = ndctl_region_get_ctx(region); struct ndctl_test *test = ndctl_get_private_data(ctx); enum ndctl_namespace_mode mode; struct ndctl_dax *dax; const char *devname; ssize_t rc; dax = get_idle_dax(region); if (!dax) return -ENXIO; devname = ndctl_dax_get_devname(dax); ndctl_dax_set_uuid(dax, uuid); ndctl_dax_set_location(dax, loc); /* * nfit_test uses vmalloc()'d resources so the only feasible * alignment is PAGE_SIZE */ ndctl_dax_set_align(dax, SZ_4K); rc = ndctl_namespace_set_enforce_mode(ndns, NDCTL_NS_MODE_DAX); if (ndctl_test_attempt(test, KERNEL_VERSION(4, 13, 0)) && rc < 0) { fprintf(stderr, "%s: failed to enforce dax mode\n", devname); return rc; } ndctl_dax_set_namespace(dax, ndns); rc = ndctl_dax_enable(dax); if (rc) { fprintf(stderr, "%s: failed to enable dax\n", devname); return rc; } mode = ndctl_namespace_get_mode(ndns); if (mode >= 0 && mode != NDCTL_NS_MODE_DAX) fprintf(stderr, "%s: expected dax mode got: %d\n", devname, mode); if (namespace->ro == (rc == 0)) { fprintf(stderr, "%s: expected dax enable %s, %s read-%s\n", devname, namespace->ro ? "failure" : "success", ndctl_region_get_devname(region), namespace->ro ? "only" : "write"); return -ENXIO; } if (dax_seed == ndctl_region_get_dax_seed(region) && dax == dax_seed) { fprintf(stderr, "%s: failed to advance dax seed\n", ndctl_region_get_devname(region)); return -ENXIO; } if (namespace->ro) { ndctl_region_set_ro(region, 0); rc = ndctl_dax_enable(dax); fprintf(stderr, "%s: failed to enable after setting rw\n", devname); ndctl_region_set_ro(region, 1); return -ENXIO; } rc = validate_dax(dax); if (rc) { fprintf(stderr, "%s: %s validate_dax failed\n", __func__, devname); return rc; } if (namespace->ro) ndctl_region_set_ro(region, 1); rc = ndctl_dax_delete(dax); if (rc) fprintf(stderr, "%s: failed to delete dax (%zd)\n", devname, rc); return rc; } static int check_dax_create(struct ndctl_region *region, struct ndctl_namespace *ndns, struct namespace *namespace) { struct dax *dax_s = namespace->dax_settings; void *buf = NULL; unsigned int i; int rc = 0; if (!dax_s) return 0; for (i = 0; i < ARRAY_SIZE(dax_s->locs); i++) { /* * The kernel enforces invalidating the previous info * block when the current uuid is does not validate with * the contents of the info block. */ dax_s->uuid[0]++; rc = __check_dax_create(region, ndns, namespace, dax_s->locs[i], dax_s->uuid); if (rc) break; } free(buf); return rc; } static int __check_pfn_create(struct ndctl_region *region, struct ndctl_namespace *ndns, struct namespace *namespace, void *buf, enum ndctl_pfn_loc loc, uuid_t uuid) { struct ndctl_pfn *pfn_seed = ndctl_region_get_pfn_seed(region); struct ndctl_ctx *ctx = ndctl_region_get_ctx(region); struct ndctl_test *test = ndctl_get_private_data(ctx); enum ndctl_namespace_mode mode; struct ndctl_pfn *pfn; const char *devname; int fd, retry = 10; char bdevpath[50]; ssize_t rc; pfn = get_idle_pfn(region); if (!pfn) return -ENXIO; devname = ndctl_pfn_get_devname(pfn); ndctl_pfn_set_uuid(pfn, uuid); ndctl_pfn_set_location(pfn, loc); /* * nfit_test uses vmalloc()'d resources so the only feasible * alignment is PAGE_SIZE */ ndctl_pfn_set_align(pfn, SZ_4K); rc = ndctl_namespace_set_enforce_mode(ndns, NDCTL_NS_MODE_MEMORY); if (ndctl_test_attempt(test, KERNEL_VERSION(4, 13, 0)) && rc < 0) { fprintf(stderr, "%s: failed to enforce pfn mode\n", devname); return rc; } ndctl_pfn_set_namespace(pfn, ndns); rc = ndctl_pfn_enable(pfn); if (rc) { fprintf(stderr, "%s: failed to enable pfn\n", devname); return rc; } mode = ndctl_namespace_get_mode(ndns); if (mode >= 0 && mode != NDCTL_NS_MODE_MEMORY) fprintf(stderr, "%s: expected fsdax mode got: %d\n", devname, mode); if (namespace->ro == (rc == 0)) { fprintf(stderr, "%s: expected pfn enable %s, %s read-%s\n", devname, namespace->ro ? "failure" : "success", ndctl_region_get_devname(region), namespace->ro ? "only" : "write"); return -ENXIO; } if (pfn_seed == ndctl_region_get_pfn_seed(region) && pfn == pfn_seed) { fprintf(stderr, "%s: failed to advance pfn seed\n", ndctl_region_get_devname(region)); return -ENXIO; } if (namespace->ro) { ndctl_region_set_ro(region, 0); rc = ndctl_pfn_enable(pfn); fprintf(stderr, "%s: failed to enable after setting rw\n", devname); ndctl_region_set_ro(region, 1); return -ENXIO; } sprintf(bdevpath, "/dev/%s", ndctl_pfn_get_block_device(pfn)); rc = -ENXIO; fd = open(bdevpath, O_RDWR|O_DIRECT); if (fd < 0) fprintf(stderr, "%s: failed to open %s\n", devname, bdevpath); while (fd >= 0) { rc = pread(fd, buf, 4096, 0); if (rc < 4096) { /* TODO: track down how this happens! */ if (errno == ENOENT && retry--) { usleep(5000); continue; } fprintf(stderr, "%s: failed to read %s: %d %zd (%s)\n", devname, bdevpath, -errno, rc, strerror(errno)); rc = -ENXIO; break; } if (write(fd, buf, 4096) < 4096) { fprintf(stderr, "%s: failed to write %s\n", devname, bdevpath); rc = -ENXIO; break; } rc = 0; break; } if (namespace->ro) ndctl_region_set_ro(region, 1); if (fd >= 0) close(fd); if (rc) return rc; rc = ndctl_pfn_delete(pfn); if (rc) fprintf(stderr, "%s: failed to delete pfn (%zd)\n", devname, rc); return rc; } static int check_pfn_create(struct ndctl_region *region, struct ndctl_namespace *ndns, struct namespace *namespace) { struct pfn *pfn_s = namespace->pfn_settings; void *buf = NULL; unsigned int i; int rc = 0; if (!pfn_s) return 0; if (posix_memalign(&buf, 4096, 4096) != 0) return -ENXIO; for (i = 0; i < ARRAY_SIZE(pfn_s->locs); i++) { /* * The kernel enforces invalidating the previous info * block when the current uuid is does not validate with * the contents of the info block. */ pfn_s->uuid[0]++; rc = __check_pfn_create(region, ndns, namespace, buf, pfn_s->locs[i], pfn_s->uuid); if (rc) break; } free(buf); return rc; } static int check_btt_size(struct ndctl_btt *btt) { unsigned long long ns_size; unsigned long sect_size; unsigned long long actual, expect; int size_select, sect_select; struct ndctl_ctx *ctx = ndctl_btt_get_ctx(btt); struct ndctl_test *test = ndctl_get_private_data(ctx); struct ndctl_namespace *ndns = ndctl_btt_get_namespace(btt); unsigned long long expect_table[][2] = { [0] = { [0] = 0x11b5400, [1] = 0x8daa000, }, [1] = { [0] = 0x13b1400, [1] = 0x9d8a000, }, [2] = { [0] = 0x1aa3600, [1] = 0xd51b000, }, }; if (!ndns) return -ENXIO; ns_size = ndctl_namespace_get_size(ndns); sect_size = ndctl_btt_get_sector_size(btt); if (sect_size >= SZ_4K) sect_select = 1; else if (sect_size >= 512) sect_select = 0; else { fprintf(stderr, "%s: %s unexpected sector size: %lx\n", __func__, ndctl_btt_get_devname(btt), sect_size); return -ENXIO; } switch (ns_size) { case SZ_18M: size_select = 0; break; case SZ_20M: size_select = 1; break; case SZ_27M: size_select = 2; break; default: fprintf(stderr, "%s: %s unexpected namespace size: %llx\n", __func__, ndctl_namespace_get_devname(ndns), ns_size); return -ENXIO; } /* prior to 4.8 btt devices did not have a size attribute */ if (!ndctl_test_attempt(test, KERNEL_VERSION(4, 8, 0))) return 0; expect = expect_table[size_select][sect_select]; actual = ndctl_btt_get_size(btt); if (expect != actual) { fprintf(stderr, "%s: namespace: %s unexpected size: %llx (expected: %llx)\n", ndctl_btt_get_devname(btt), ndctl_namespace_get_devname(ndns), actual, expect); return -ENXIO; } return 0; } static int check_btt_create(struct ndctl_region *region, struct ndctl_namespace *ndns, struct namespace *namespace) { struct ndctl_ctx *ctx = ndctl_region_get_ctx(region); struct ndctl_test *test = ndctl_get_private_data(ctx); struct btt *btt_s = namespace->btt_settings; int i, fd, retry = 10; struct ndctl_btt *btt; const char *devname; char bdevpath[50]; void *buf = NULL; ssize_t rc = 0; if (!namespace->btt_settings) return 0; if (posix_memalign(&buf, 4096, 4096) != 0) return -ENXIO; for (i = 0; i < btt_s->num_sector_sizes; i++) { struct ndctl_btt *btt_seed = ndctl_region_get_btt_seed(region); enum ndctl_namespace_mode mode; btt = get_idle_btt(region); if (!btt) goto err; devname = ndctl_btt_get_devname(btt); ndctl_btt_set_uuid(btt, btt_s->uuid); ndctl_btt_set_sector_size(btt, btt_s->sector_sizes[i]); rc = ndctl_namespace_set_enforce_mode(ndns, NDCTL_NS_MODE_SECTOR); if (ndctl_test_attempt(test, KERNEL_VERSION(4, 13, 0)) && rc < 0) { fprintf(stderr, "%s: failed to enforce btt mode\n", devname); goto err; } ndctl_btt_set_namespace(btt, ndns); rc = ndctl_btt_enable(btt); if (namespace->ro == (rc == 0)) { fprintf(stderr, "%s: expected btt enable %s, %s read-%s\n", devname, namespace->ro ? "failure" : "success", ndctl_region_get_devname(region), namespace->ro ? "only" : "write"); goto err; } /* prior to v4.5 the mode attribute did not exist */ if (ndctl_test_attempt(test, KERNEL_VERSION(4, 5, 0))) { mode = ndctl_namespace_get_mode(ndns); if (mode >= 0 && mode != NDCTL_NS_MODE_SECTOR) fprintf(stderr, "%s: expected safe mode got: %d\n", devname, mode); } /* prior to v4.13 the expected sizes were different due to BTT1.1 */ if (ndctl_test_attempt(test, KERNEL_VERSION(4, 13, 0))) { rc = check_btt_size(btt); if (rc) goto err; } if (btt_seed == ndctl_region_get_btt_seed(region) && btt == btt_seed) { fprintf(stderr, "%s: failed to advance btt seed\n", ndctl_region_get_devname(region)); goto err; } if (namespace->ro) { ndctl_region_set_ro(region, 0); rc = ndctl_btt_enable(btt); fprintf(stderr, "%s: failed to enable after setting rw\n", devname); ndctl_region_set_ro(region, 1); goto err; } sprintf(bdevpath, "/dev/%s", ndctl_btt_get_block_device(btt)); rc = -ENXIO; fd = open(bdevpath, O_RDWR|O_DIRECT); if (fd < 0) fprintf(stderr, "%s: failed to open %s\n", devname, bdevpath); while (fd >= 0) { rc = pread(fd, buf, 4096, 0); if (rc < 4096) { /* TODO: track down how this happens! */ if (errno == ENOENT && retry--) { usleep(5000); continue; } fprintf(stderr, "%s: failed to read %s: %d %zd (%s)\n", devname, bdevpath, -errno, rc, strerror(errno)); rc = -ENXIO; break; } if (write(fd, buf, 4096) < 4096) { fprintf(stderr, "%s: failed to write %s\n", devname, bdevpath); rc = -ENXIO; break; } rc = 0; break; } if (namespace->ro) ndctl_region_set_ro(region, 1); if (fd >= 0) close(fd); if (rc) break; rc = ndctl_btt_delete(btt); if (rc) fprintf(stderr, "%s: failed to delete btt (%zd)\n", devname, rc); } free(buf); return rc; err: free(buf); return -ENXIO; } static int configure_namespace(struct ndctl_region *region, struct ndctl_namespace *ndns, struct namespace *namespace, unsigned long lbasize, enum ns_mode mode) { char devname[50]; int rc; if (namespace->do_configure <= 0) return 0; snprintf(devname, sizeof(devname), "namespace%d.%d", ndctl_region_get_id(region), namespace->id); if (!ndctl_namespace_is_configured(ndns)) { rc = ndctl_namespace_set_uuid(ndns, namespace->uuid); if (rc) fprintf(stderr, "%s: set_uuid failed: %d\n", devname, rc); rc = ndctl_namespace_set_alt_name(ndns, devname); if (rc) fprintf(stderr, "%s: set_alt_name failed: %d\n", devname, rc); rc = ndctl_namespace_set_size(ndns, namespace->size); if (rc) fprintf(stderr, "%s: set_size failed: %d\n", devname, rc); } if (lbasize) { rc = ndctl_namespace_set_sector_size(ndns, lbasize); if (rc) fprintf(stderr, "%s: set_sector_size (%lu) failed: %d\n", devname, lbasize, rc); } rc = ndctl_namespace_is_configured(ndns); if (rc < 1) fprintf(stderr, "%s: is_configured: %d\n", devname, rc); if (mode == BTT) { rc = check_btt_create(region, ndns, namespace); if (rc < 0) { fprintf(stderr, "%s: failed to create btt\n", devname); return rc; } } if (mode == PFN) { rc = check_pfn_create(region, ndns, namespace); if (rc < 0) { fprintf(stderr, "%s: failed to create pfn\n", devname); return rc; } } if (mode == DAX) { rc = check_dax_create(region, ndns, namespace); if (rc < 0) { fprintf(stderr, "%s: failed to create dax\n", devname); return rc; } } rc = ndctl_namespace_enable(ndns); if (rc < 0) fprintf(stderr, "%s: enable: %d\n", devname, rc); return rc; } static int check_pfn_autodetect(struct ndctl_bus *bus, struct ndctl_namespace *ndns, void *buf, struct namespace *namespace) { struct ndctl_region *region = ndctl_namespace_get_region(ndns); struct ndctl_ctx *ctx = ndctl_region_get_ctx(region); const char *devname = ndctl_namespace_get_devname(ndns); struct ndctl_test *test = ndctl_get_private_data(ctx); struct pfn *auto_pfn = namespace->pfn_settings; struct ndctl_pfn *pfn, *found = NULL; enum ndctl_namespace_mode mode; ssize_t rc = -ENXIO; char bdev[50]; int fd, ro; ndctl_pfn_foreach(region, pfn) { struct ndctl_namespace *pfn_ndns; uuid_t uu; ndctl_pfn_get_uuid(pfn, uu); if (uuid_compare(uu, auto_pfn->uuid) != 0) continue; if (!ndctl_pfn_is_enabled(pfn)) continue; pfn_ndns = ndctl_pfn_get_namespace(pfn); if (strcmp(ndctl_namespace_get_devname(pfn_ndns), devname) != 0) continue; fprintf(stderr, "%s: pfn_ndns: %p ndns: %p\n", __func__, pfn_ndns, ndns); found = pfn; break; } if (!found) return -ENXIO; mode = ndctl_namespace_get_enforce_mode(ndns); if (ndctl_test_attempt(test, KERNEL_VERSION(4, 13, 0)) && mode != NDCTL_NS_MODE_MEMORY) { fprintf(stderr, "%s expected enforce_mode pfn\n", devname); return -ENXIO; } sprintf(bdev, "/dev/%s", ndctl_pfn_get_block_device(pfn)); fd = open(bdev, O_RDONLY); if (fd < 0) return -ENXIO; rc = ioctl(fd, BLKROGET, &ro); if (rc < 0) { fprintf(stderr, "%s: failed to open %s\n", __func__, bdev); rc = -ENXIO; goto out; } close(fd); fd = -1; rc = -ENXIO; if (ro != namespace->ro) { fprintf(stderr, "%s: read-%s expected read-%s by default\n", bdev, ro ? "only" : "write", namespace->ro ? "only" : "write"); goto out; } /* destroy pfn device */ ndctl_pfn_delete(found); /* clear read-write, and enable raw mode */ ndctl_region_set_ro(region, 0); ndctl_namespace_set_raw_mode(ndns, 1); ndctl_namespace_enable(ndns); /* destroy pfn metadata */ sprintf(bdev, "/dev/%s", ndctl_namespace_get_block_device(ndns)); fd = open(bdev, O_RDWR|O_DIRECT|O_EXCL); if (fd < 0) { fprintf(stderr, "%s: failed to open %s to destroy pfn\n", devname, bdev); goto out; } memset(buf, 0, 4096); rc = pwrite(fd, buf, 4096, 4096); if (rc < 4096) { rc = -ENXIO; fprintf(stderr, "%s: failed to overwrite pfn on %s\n", devname, bdev); } out: ndctl_region_set_ro(region, namespace->ro); ndctl_namespace_set_raw_mode(ndns, 0); if (fd >= 0) close(fd); return rc; } static int check_dax_autodetect(struct ndctl_bus *bus, struct ndctl_namespace *ndns, void *buf, struct namespace *namespace) { struct ndctl_region *region = ndctl_namespace_get_region(ndns); struct ndctl_ctx *ctx = ndctl_region_get_ctx(region); const char *devname = ndctl_namespace_get_devname(ndns); struct ndctl_test *test = ndctl_get_private_data(ctx); struct dax *auto_dax = namespace->dax_settings; struct ndctl_dax *dax, *found = NULL; enum ndctl_namespace_mode mode; ssize_t rc = -ENXIO; char bdev[50]; int fd; ndctl_dax_foreach(region, dax) { struct ndctl_namespace *dax_ndns; uuid_t uu; ndctl_dax_get_uuid(dax, uu); if (uuid_compare(uu, auto_dax->uuid) != 0) continue; if (!ndctl_dax_is_enabled(dax)) continue; dax_ndns = ndctl_dax_get_namespace(dax); if (strcmp(ndctl_namespace_get_devname(dax_ndns), devname) != 0) continue; fprintf(stderr, "%s: dax_ndns: %p ndns: %p\n", __func__, dax_ndns, ndns); found = dax; break; } if (!found) return -ENXIO; mode = ndctl_namespace_get_enforce_mode(ndns); if (ndctl_test_attempt(test, KERNEL_VERSION(4, 13, 0)) && mode != NDCTL_NS_MODE_DAX) { fprintf(stderr, "%s expected enforce_mode dax\n", devname); return -ENXIO; } rc = validate_dax(dax); if (rc) { fprintf(stderr, "%s: %s validate_dax failed\n", __func__, devname); return rc; } rc = -ENXIO; /* destroy dax device */ ndctl_dax_delete(found); /* clear read-write, and enable raw mode */ ndctl_region_set_ro(region, 0); ndctl_namespace_set_raw_mode(ndns, 1); ndctl_namespace_enable(ndns); /* destroy dax metadata */ sprintf(bdev, "/dev/%s", ndctl_namespace_get_block_device(ndns)); fd = open(bdev, O_RDWR|O_DIRECT|O_EXCL); if (fd < 0) { fprintf(stderr, "%s: failed to open %s to destroy dax\n", devname, bdev); goto out; } memset(buf, 0, 4096); rc = pwrite(fd, buf, 4096, 4096); if (rc < 4096) { rc = -ENXIO; fprintf(stderr, "%s: failed to overwrite dax on %s\n", devname, bdev); } out: ndctl_region_set_ro(region, namespace->ro); ndctl_namespace_set_raw_mode(ndns, 0); if (fd >= 0) close(fd); return rc; } static int check_btt_autodetect(struct ndctl_bus *bus, struct ndctl_namespace *ndns, void *buf, struct namespace *namespace) { struct ndctl_region *region = ndctl_namespace_get_region(ndns); struct ndctl_ctx *ctx = ndctl_region_get_ctx(region); const char *devname = ndctl_namespace_get_devname(ndns); struct ndctl_test *test = ndctl_get_private_data(ctx); struct btt *auto_btt = namespace->btt_settings; struct ndctl_btt *btt, *found = NULL; enum ndctl_namespace_mode mode; ssize_t rc = -ENXIO; char bdev[50]; int fd, ro; ndctl_btt_foreach(region, btt) { struct ndctl_namespace *btt_ndns; uuid_t uu; ndctl_btt_get_uuid(btt, uu); if (uuid_compare(uu, auto_btt->uuid) != 0) continue; if (!ndctl_btt_is_enabled(btt)) continue; btt_ndns = ndctl_btt_get_namespace(btt); if (!btt_ndns || strcmp(ndctl_namespace_get_devname(btt_ndns), devname) != 0) continue; fprintf(stderr, "%s: btt_ndns: %p ndns: %p\n", __func__, btt_ndns, ndns); found = btt; break; } if (!found) return -ENXIO; mode = ndctl_namespace_get_enforce_mode(ndns); if (ndctl_test_attempt(test, KERNEL_VERSION(4, 13, 0)) && mode != NDCTL_NS_MODE_SECTOR) { fprintf(stderr, "%s expected enforce_mode btt\n", devname); return -ENXIO; } sprintf(bdev, "/dev/%s", ndctl_btt_get_block_device(btt)); fd = open(bdev, O_RDONLY); if (fd < 0) return -ENXIO; rc = ioctl(fd, BLKROGET, &ro); if (rc < 0) { fprintf(stderr, "%s: failed to open %s\n", __func__, bdev); rc = -ENXIO; goto out; } close(fd); fd = -1; rc = -ENXIO; if (ro != namespace->ro) { fprintf(stderr, "%s: read-%s expected read-%s by default\n", bdev, ro ? "only" : "write", namespace->ro ? "only" : "write"); goto out; } /* destroy btt device */ ndctl_btt_delete(found); /* clear read-write, and enable raw mode */ ndctl_region_set_ro(region, 0); ndctl_namespace_set_raw_mode(ndns, 1); ndctl_namespace_enable(ndns); /* destroy btt metadata */ sprintf(bdev, "/dev/%s", ndctl_namespace_get_block_device(ndns)); fd = open(bdev, O_RDWR|O_DIRECT|O_EXCL); if (fd < 0) { fprintf(stderr, "%s: failed to open %s to destroy btt\n", devname, bdev); goto out; } memset(buf, 0, 4096); /* Delete both the first and second 4K pages */ rc = pwrite(fd, buf, 4096, 4096); if (rc < 4096) { rc = -ENXIO; fprintf(stderr, "%s: failed to overwrite btt on %s\n", devname, bdev); goto out; } rc = pwrite(fd, buf, 4096, 0); if (rc < 4096) { rc = -ENXIO; fprintf(stderr, "%s: failed to overwrite btt on %s\n", devname, bdev); } out: ndctl_region_set_ro(region, namespace->ro); ndctl_namespace_set_raw_mode(ndns, 0); if (fd >= 0) close(fd); return rc; } static int validate_bdev(const char *devname, struct ndctl_btt *btt, struct ndctl_pfn *pfn, struct ndctl_namespace *ndns, struct namespace *namespace, void *buf) { struct ndctl_region *region = ndctl_namespace_get_region(ndns); char bdevpath[50]; int fd, rc, ro; if (btt) sprintf(bdevpath, "/dev/%s", ndctl_btt_get_block_device(btt)); else if (pfn) sprintf(bdevpath, "/dev/%s", ndctl_pfn_get_block_device(pfn)); else sprintf(bdevpath, "/dev/%s", ndctl_namespace_get_block_device(ndns)); fd = open(bdevpath, O_RDONLY); if (fd < 0) { fprintf(stderr, "%s: failed to open(%s, O_RDONLY)\n", devname, bdevpath); return -ENXIO; } rc = ioctl(fd, BLKROGET, &ro); if (rc < 0) { fprintf(stderr, "%s: BLKROGET failed\n", devname); rc = -errno; goto out; } if (namespace->ro != ro) { fprintf(stderr, "%s: read-%s expected: read-%s\n", devname, ro ? "only" : "write", namespace->ro ? "only" : "write"); rc = -ENXIO; goto out; } ro = 0; rc = ndctl_region_set_ro(region, ro); if (rc < 0) { fprintf(stderr, "%s: ndctl_region_set_ro failed\n", devname); rc = -errno; goto out; } rc = ioctl(fd, BLKROSET, &ro); if (rc < 0) { fprintf(stderr, "%s: BLKROSET failed\n", devname); rc = -errno; goto out; } close(fd); fd = open(bdevpath, O_RDWR|O_DIRECT); if (fd < 0) { fprintf(stderr, "%s: failed to open(%s, O_RDWR|O_DIRECT)\n", devname, bdevpath); return -ENXIO; } if (read(fd, buf, 4096) < 4096) { fprintf(stderr, "%s: failed to read %s\n", devname, bdevpath); rc = -ENXIO; goto out; } if (write(fd, buf, 4096) < 4096) { fprintf(stderr, "%s: failed to write %s\n", devname, bdevpath); rc = -ENXIO; goto out; } rc = ndctl_region_set_ro(region, namespace->ro); if (rc < 0) { fprintf(stderr, "%s: ndctl_region_set_ro reset failed\n", devname); rc = -errno; goto out; } rc = 0; out: close(fd); return rc; } static int validate_write_cache(struct ndctl_namespace *ndns) { const char *devname = ndctl_namespace_get_devname(ndns); int wc, mode, type, rc; type = ndctl_namespace_get_type(ndns); mode = ndctl_namespace_get_mode(ndns); wc = ndctl_namespace_write_cache_is_enabled(ndns); if ((type == ND_DEVICE_NAMESPACE_PMEM || type == ND_DEVICE_NAMESPACE_IO) && (mode == NDCTL_NS_MODE_FSDAX || mode == NDCTL_NS_MODE_RAW)) { if (wc != 1) { fprintf(stderr, "%s: expected write_cache enabled\n", devname); return -ENXIO; } rc = ndctl_namespace_disable_write_cache(ndns); if (rc) { fprintf(stderr, "%s: failed to disable write_cache\n", devname); return rc; } rc = ndctl_namespace_write_cache_is_enabled(ndns); if (rc != 0) { fprintf(stderr, "%s: write_cache could not be disabled\n", devname); return rc; } rc = ndctl_namespace_enable_write_cache(ndns); if (rc) { fprintf(stderr, "%s: failed to re-enable write_cache\n", devname); return rc; } rc = ndctl_namespace_write_cache_is_enabled(ndns); if (rc != 1) { fprintf(stderr, "%s: write_cache could not be re-enabled\n", devname); return rc; } } else { if (wc == 0 || wc == 1) { fprintf(stderr, "%s: expected write_cache to be absent\n", devname); return -ENXIO; } } return 0; } static int check_namespaces(struct ndctl_region *region, struct namespace **namespaces, enum ns_mode mode) { struct ndctl_ctx *ctx = ndctl_region_get_ctx(region); struct ndctl_test *test = ndctl_get_private_data(ctx); struct ndctl_bus *bus = ndctl_region_get_bus(region); struct ndctl_namespace **ndns_save; struct namespace *namespace; int i, j, rc, retry_cnt = 1; void *buf = NULL, *__ndns_save; char devname[50]; if (posix_memalign(&buf, 4096, 4096) != 0) return -ENOMEM; for (i = 0; (namespace = namespaces[i]); i++) if (namespace->do_configure >= 0) namespace->do_configure = 1; retry: ndns_save = NULL; for (i = 0; (namespace = namespaces[i]); i++) { uuid_t uu; struct ndctl_namespace *ndns; unsigned long _sizes[] = { 0 }, *sector_sizes = _sizes; int num_sector_sizes = (int) ARRAY_SIZE(_sizes); snprintf(devname, sizeof(devname), "namespace%d.%d", ndctl_region_get_id(region), namespace->id); ndns = get_namespace_by_id(region, namespace); if (!ndns) { fprintf(stderr, "%s: failed to find namespace\n", devname); break; } if (ndctl_region_get_type(region) == ND_DEVICE_REGION_PMEM && !ndctl_test_attempt(test, KERNEL_VERSION(4, 13, 0))) /* pass, no sector_size support for pmem prior to 4.13 */; else { num_sector_sizes = namespace->num_sector_sizes; sector_sizes = namespace->sector_sizes; } for (j = 0; j < num_sector_sizes; j++) { struct btt *btt_s = NULL; struct pfn *pfn_s = NULL; struct dax *dax_s = NULL; struct ndctl_btt *btt = NULL; struct ndctl_pfn *pfn = NULL; struct ndctl_dax *dax = NULL; rc = configure_namespace(region, ndns, namespace, sector_sizes[j], mode); if (rc < 0) { fprintf(stderr, "%s: failed to configure namespace\n", devname); break; } if (strcmp(ndctl_namespace_get_type_name(ndns), namespace->type) != 0) { fprintf(stderr, "%s: expected type: %s got: %s\n", devname, ndctl_namespace_get_type_name(ndns), namespace->type); rc = -ENXIO; break; } /* * On the second time through this loop we skip * establishing btt|pfn since * check_{btt|pfn}_autodetect() destroyed the * inital instance. */ if (mode == BTT) { btt_s = namespace->do_configure > 0 ? namespace->btt_settings : NULL; btt = ndctl_namespace_get_btt(ndns); if (!!btt_s != !!btt) { fprintf(stderr, "%s expected btt %s by default\n", devname, namespace->btt_settings ? "enabled" : "disabled"); rc = -ENXIO; break; } } if (mode == PFN) { pfn_s = namespace->do_configure > 0 ? namespace->pfn_settings : NULL; pfn = ndctl_namespace_get_pfn(ndns); if (!!pfn_s != !!pfn) { fprintf(stderr, "%s expected pfn %s by default\n", devname, namespace->pfn_settings ? "enabled" : "disabled"); rc = -ENXIO; break; } } if (mode == DAX) { dax_s = namespace->do_configure > 0 ? namespace->dax_settings : NULL; dax = ndctl_namespace_get_dax(ndns); if (!!dax_s != !!dax) { fprintf(stderr, "%s expected dax %s by default\n", devname, namespace->dax_settings ? "enabled" : "disabled"); rc = -ENXIO; break; } } if (!btt_s && !pfn_s && !dax_s && !ndctl_namespace_is_enabled(ndns)) { fprintf(stderr, "%s: expected enabled by default\n", devname); rc = -ENXIO; break; } if (namespace->size != ndctl_namespace_get_size(ndns)) { fprintf(stderr, "%s: expected size: %#llx got: %#llx\n", devname, namespace->size, ndctl_namespace_get_size(ndns)); rc = -ENXIO; break; } if (sector_sizes[j] && sector_sizes[j] != ndctl_namespace_get_sector_size(ndns)) { fprintf(stderr, "%s: expected lbasize: %#lx got: %#x\n", devname, sector_sizes[j], ndctl_namespace_get_sector_size(ndns)); rc = -ENXIO; break; } ndctl_namespace_get_uuid(ndns, uu); if (uuid_compare(uu, namespace->uuid) != 0) { char expect[40], actual[40]; uuid_unparse(uu, actual); uuid_unparse(namespace->uuid, expect); fprintf(stderr, "%s: expected uuid: %s got: %s\n", devname, expect, actual); rc = -ENXIO; break; } if (namespace->check_alt_name && strcmp(ndctl_namespace_get_alt_name(ndns), devname) != 0) { fprintf(stderr, "%s: expected alt_name: %s got: %s\n", devname, devname, ndctl_namespace_get_alt_name(ndns)); rc = -ENXIO; break; } if (dax) rc = validate_dax(dax); else rc = validate_bdev(devname, btt, pfn, ndns, namespace, buf); if (rc) { fprintf(stderr, "%s: %s validate_%s failed\n", __func__, devname, dax ? "dax" : "bdev"); break; } rc = validate_write_cache(ndns); if (rc) { fprintf(stderr, "%s: %s validate_write_cache failed\n", __func__, devname); break; } if (ndctl_namespace_disable_invalidate(ndns) < 0) { fprintf(stderr, "%s: failed to disable\n", devname); rc = -ENXIO; break; } if (ndctl_namespace_enable(ndns) < 0) { fprintf(stderr, "%s: failed to enable\n", devname); rc = -ENXIO; break; } if (btt_s && check_btt_autodetect(bus, ndns, buf, namespace) < 0) { fprintf(stderr, "%s, failed btt autodetect\n", devname); rc = -ENXIO; break; } if (pfn_s && check_pfn_autodetect(bus, ndns, buf, namespace) < 0) { fprintf(stderr, "%s, failed pfn autodetect\n", devname); rc = -ENXIO; break; } if (dax_s && check_dax_autodetect(bus, ndns, buf, namespace) < 0) { fprintf(stderr, "%s, failed dax autodetect\n", devname); rc = -ENXIO; break; } /* * if the namespace is being tested with a btt, there is no * point testing different sector sizes for the namespace itself */ if (btt_s || pfn_s || dax_s) break; /* * If this is the last sector size being tested, don't disable * the namespace */ if (j == num_sector_sizes - 1) break; /* * If we're in the second time through this, don't loop for * different sector sizes as ->do_configure is disabled */ if (!retry_cnt) break; if (ndctl_namespace_disable_invalidate(ndns) < 0) { fprintf(stderr, "%s: failed to disable\n", devname); break; } } namespace->do_configure = 0; __ndns_save = realloc(ndns_save, sizeof(struct ndctl_namespace *) * (i + 1)); if (!__ndns_save) { fprintf(stderr, "%s: %s() -ENOMEM\n", devname, __func__); rc = -ENOMEM; break; } else { ndns_save = __ndns_save; ndns_save[i] = ndns; } if (rc) break; } if (namespace || ndctl_region_disable_preserve(region) != 0) { rc = -ENXIO; if (!namespace) fprintf(stderr, "failed to disable region%d\n", ndctl_region_get_id(region)); goto out; } /* * On the second time through configure_namespace() is skipped * to test assembling namespace(s) from an existing label set */ if (retry_cnt--) { ndctl_region_enable(region); free(ndns_save); goto retry; } rc = 0; for (i--; i >= 0; i--) { struct ndctl_namespace *ndns = ndns_save[i]; snprintf(devname, sizeof(devname), "namespace%d.%d", ndctl_region_get_id(region), ndctl_namespace_get_id(ndns)); if (ndctl_namespace_is_valid(ndns)) { fprintf(stderr, "%s: failed to invalidate\n", devname); rc = -ENXIO; break; } } ndctl_region_cleanup(region); out: free(ndns_save); free(buf); return rc; } static int check_btt_supported_sectors(struct ndctl_btt *btt, struct btt *expect_btt) { int s, t; char devname[50]; snprintf(devname, sizeof(devname), "btt%d", ndctl_btt_get_id(btt)); for (s = 0; s < expect_btt->num_sector_sizes; s++) { for (t = 0; t < expect_btt->num_sector_sizes; t++) { if (ndctl_btt_get_supported_sector_size(btt, t) == expect_btt->sector_sizes[s]) break; } if (t >= expect_btt->num_sector_sizes) { fprintf(stderr, "%s: expected sector_size: %d to be supported\n", devname, expect_btt->sector_sizes[s]); return -ENXIO; } } return 0; } static int check_btts(struct ndctl_region *region, struct btt **btts) { struct btt *btt_s; int i; for (i = 0; (btt_s = btts[i]); i++) { struct ndctl_btt *btt; char devname[50]; uuid_t btt_uuid; int rc; btt = get_idle_btt(region); if (!btt) { fprintf(stderr, "failed to find idle btt\n"); return -ENXIO; } snprintf(devname, sizeof(devname), "btt%d", ndctl_btt_get_id(btt)); ndctl_btt_get_uuid(btt, btt_uuid); if (uuid_compare(btt_uuid, btt_s->uuid) != 0) { char expect[40], actual[40]; uuid_unparse(btt_uuid, actual); uuid_unparse(btt_s->uuid, expect); fprintf(stderr, "%s: expected uuid: %s got: %s\n", devname, expect, actual); return -ENXIO; } if (ndctl_btt_get_num_sector_sizes(btt) != btt_s->num_sector_sizes) { fprintf(stderr, "%s: expected num_sector_sizes: %d got: %d\n", devname, btt_s->num_sector_sizes, ndctl_btt_get_num_sector_sizes(btt)); } rc = check_btt_supported_sectors(btt, btt_s); if (rc) return rc; if (btt_s->enabled && ndctl_btt_is_enabled(btt)) { fprintf(stderr, "%s: expected disabled by default\n", devname); return -ENXIO; } } return 0; } struct check_cmd { int (*check_fn)(struct ndctl_bus *bus, struct ndctl_dimm *dimm, struct check_cmd *check); struct ndctl_cmd *cmd; struct ndctl_test *test; }; static struct check_cmd *check_cmds; static int check_get_config_size(struct ndctl_bus *bus, struct ndctl_dimm *dimm, struct check_cmd *check) { struct ndctl_cmd *cmd; int rc; if (check->cmd != NULL) { fprintf(stderr, "%s: dimm: %#x expected a NULL command, by default\n", __func__, ndctl_dimm_get_handle(dimm)); return -ENXIO; } cmd = ndctl_dimm_cmd_new_cfg_size(dimm); if (!cmd) { fprintf(stderr, "%s: dimm: %#x failed to create cmd\n", __func__, ndctl_dimm_get_handle(dimm)); return -ENOTTY; } rc = ndctl_cmd_submit(cmd); if (rc < 0) { fprintf(stderr, "%s: dimm: %#x failed to submit cmd: %d\n", __func__, ndctl_dimm_get_handle(dimm), rc); ndctl_cmd_unref(cmd); return rc; } if (ndctl_cmd_cfg_size_get_size(cmd) != SZ_128K) { fprintf(stderr, "%s: dimm: %#x expect size: %d got: %d\n", __func__, ndctl_dimm_get_handle(dimm), SZ_128K, ndctl_cmd_cfg_size_get_size(cmd)); ndctl_cmd_unref(cmd); return -ENXIO; } check->cmd = cmd; return 0; } static int check_get_config_data(struct ndctl_bus *bus, struct ndctl_dimm *dimm, struct check_cmd *check) { struct ndctl_cmd *cmd_size = check_cmds[ND_CMD_GET_CONFIG_SIZE].cmd; struct ndctl_cmd *cmd = ndctl_dimm_cmd_new_cfg_read(cmd_size); static char buf[SZ_128K]; ssize_t rc; if (!cmd) { fprintf(stderr, "%s: dimm: %#x failed to create cmd\n", __func__, ndctl_dimm_get_handle(dimm)); return -ENOTTY; } rc = ndctl_cmd_submit(cmd); if (rc < 0) { fprintf(stderr, "%s: dimm: %#x failed to submit cmd: %zd\n", __func__, ndctl_dimm_get_handle(dimm), rc); ndctl_cmd_unref(cmd); return rc; } rc = ndctl_cmd_cfg_read_get_data(cmd, buf, SZ_128K, 0); if (rc != SZ_128K) { fprintf(stderr, "%s: dimm: %#x expected read %d bytes, got: %zd\n", __func__, ndctl_dimm_get_handle(dimm), SZ_128K, rc); ndctl_cmd_unref(cmd); return -ENXIO; } check->cmd = cmd; return 0; } static int check_set_config_data(struct ndctl_bus *bus, struct ndctl_dimm *dimm, struct check_cmd *check) { struct ndctl_cmd *cmd_read = check_cmds[ND_CMD_GET_CONFIG_DATA].cmd; struct ndctl_cmd *cmd = ndctl_dimm_cmd_new_cfg_write(cmd_read); char buf[20], result[sizeof(buf)]; int rc; if (!cmd) { fprintf(stderr, "%s: dimm: %#x failed to create cmd\n", __func__, ndctl_dimm_get_handle(dimm)); return -ENOTTY; } memset(buf, 0, sizeof(buf)); ndctl_cmd_cfg_write_set_data(cmd, buf, sizeof(buf), 0); rc = ndctl_cmd_submit(cmd); if (rc < 0) { fprintf(stderr, "%s: dimm: %#x failed to submit cmd: %d\n", __func__, ndctl_dimm_get_handle(dimm), rc); ndctl_cmd_unref(cmd); return rc; } rc = ndctl_cmd_submit(cmd_read); if (rc < 0) { fprintf(stderr, "%s: dimm: %#x failed to submit read1: %d\n", __func__, ndctl_dimm_get_handle(dimm), rc); ndctl_cmd_unref(cmd); return rc; } ndctl_cmd_cfg_read_get_data(cmd_read, result, sizeof(result), 0); if (memcmp(result, buf, sizeof(result)) != 0) { fprintf(stderr, "%s: dimm: %#x read1 data miscompare: %d\n", __func__, ndctl_dimm_get_handle(dimm), rc); ndctl_cmd_unref(cmd); return -ENXIO; } sprintf(buf, "dimm-%#x", ndctl_dimm_get_handle(dimm)); ndctl_cmd_cfg_write_set_data(cmd, buf, sizeof(buf), 0); rc = ndctl_cmd_submit(cmd); if (rc < 0) { fprintf(stderr, "%s: dimm: %#x failed to submit cmd: %d\n", __func__, ndctl_dimm_get_handle(dimm), rc); ndctl_cmd_unref(cmd); return rc; } rc = ndctl_cmd_submit(cmd_read); if (rc < 0) { fprintf(stderr, "%s: dimm: %#x failed to submit read2: %d\n", __func__, ndctl_dimm_get_handle(dimm), rc); ndctl_cmd_unref(cmd); return rc; } ndctl_cmd_cfg_read_get_data(cmd_read, result, sizeof(result), 0); if (memcmp(result, buf, sizeof(result)) != 0) { fprintf(stderr, "%s: dimm: %#x read2 data miscompare: %d\n", __func__, ndctl_dimm_get_handle(dimm), rc); ndctl_cmd_unref(cmd); return rc; } check->cmd = cmd; return 0; } #define __check_smart(dimm, cmd, field, mask) ({ \ if ((ndctl_cmd_smart_get_##field(cmd) & mask) != smart_data.field) { \ fprintf(stderr, "%s dimm: %#x expected \'" #field \ "\' %#x got: %#x\n", __func__, \ ndctl_dimm_get_handle(dimm), \ smart_data.field, \ ndctl_cmd_smart_get_##field(cmd)); \ ndctl_cmd_unref(cmd); \ return -ENXIO; \ } \ }) /* * Note, this is not a command payload, this is just a namespace for * smart parameters. */ struct smart { unsigned int flags, health, temperature, spares, alarm_flags, life_used, shutdown_state, shutdown_count, vendor_size; }; static int check_smart(struct ndctl_bus *bus, struct ndctl_dimm *dimm, struct check_cmd *check) { static const struct smart smart_data = { .flags = ND_SMART_HEALTH_VALID | ND_SMART_TEMP_VALID | ND_SMART_SPARES_VALID | ND_SMART_ALARM_VALID | ND_SMART_USED_VALID | ND_SMART_SHUTDOWN_VALID, .health = ND_SMART_NON_CRITICAL_HEALTH, .temperature = 23 * 16, .spares = 75, .alarm_flags = ND_SMART_SPARE_TRIP | ND_SMART_TEMP_TRIP, .life_used = 5, .shutdown_state = 0, .shutdown_count = 42, .vendor_size = 0, }; struct ndctl_cmd *cmd = ndctl_dimm_cmd_new_smart(dimm); int rc; if (!cmd) { fprintf(stderr, "%s: dimm: %#x failed to create cmd\n", __func__, ndctl_dimm_get_handle(dimm)); return -ENXIO; } rc = ndctl_cmd_submit(cmd); if (rc < 0) { fprintf(stderr, "%s: dimm: %#x failed to submit cmd: %d\n", __func__, ndctl_dimm_get_handle(dimm), rc); ndctl_cmd_unref(cmd); return rc; } __check_smart(dimm, cmd, flags, ~(ND_SMART_CTEMP_VALID | ND_SMART_SHUTDOWN_COUNT_VALID)); __check_smart(dimm, cmd, health, -1); __check_smart(dimm, cmd, temperature, -1); __check_smart(dimm, cmd, spares, -1); __check_smart(dimm, cmd, alarm_flags, -1); __check_smart(dimm, cmd, life_used, -1); __check_smart(dimm, cmd, shutdown_state, -1); __check_smart(dimm, cmd, vendor_size, -1); if (ndctl_cmd_smart_get_flags(cmd) & ND_SMART_SHUTDOWN_COUNT_VALID) __check_smart(dimm, cmd, shutdown_count, -1); check->cmd = cmd; return 0; } #define __check_smart_threshold(dimm, cmd, field) ({ \ if (ndctl_cmd_smart_threshold_get_##field(cmd) != smart_t_data.field) { \ fprintf(stderr, "%s dimm: %#x expected \'" #field \ "\' %#x got: %#x\n", __func__, \ ndctl_dimm_get_handle(dimm), \ smart_t_data.field, \ ndctl_cmd_smart_threshold_get_##field(cmd)); \ ndctl_cmd_unref(cmd_set); \ ndctl_cmd_unref(cmd); \ return -ENXIO; \ } \ }) /* * Note, this is not a command payload, this is just a namespace for * smart_threshold parameters. */ struct smart_threshold { unsigned int alarm_control, media_temperature, ctrl_temperature, spares; }; static int check_smart_threshold(struct ndctl_bus *bus, struct ndctl_dimm *dimm, struct check_cmd *check) { static const struct smart_threshold smart_t_data = { .alarm_control = ND_SMART_SPARE_TRIP | ND_SMART_TEMP_TRIP, .media_temperature = 40 * 16, .ctrl_temperature = 30 * 16, .spares = 5, }; struct ndctl_cmd *cmd = ndctl_dimm_cmd_new_smart_threshold(dimm); struct ndctl_cmd *cmd_smart = check_cmds[ND_CMD_SMART].cmd; struct ndctl_cmd *cmd_set; struct timeval tm; char buf[4096]; fd_set fds; int rc, fd; if (!cmd) { fprintf(stderr, "%s: dimm: %#x failed to create cmd\n", __func__, ndctl_dimm_get_handle(dimm)); return -ENXIO; } fd = ndctl_dimm_get_health_eventfd(dimm); FD_ZERO(&fds); FD_SET(fd, &fds); rc = pread(fd, buf, sizeof(buf), 0); tm.tv_sec = 0; tm.tv_usec = 500; rc = select(fd + 1, NULL, NULL, &fds, &tm); if (rc) { fprintf(stderr, "%s: expected health event timeout\n", ndctl_dimm_get_devname(dimm)); return -ENXIO; } /* * Starting with v4.9 smart threshold requests trigger the file * descriptor returned by ndctl_dimm_get_health_eventfd(). */ if (ndctl_test_attempt(check->test, KERNEL_VERSION(4, 9, 0))) { int pid = fork(); if (pid == 0) { FD_ZERO(&fds); FD_SET(fd, &fds); tm.tv_sec = 5; tm.tv_usec = 0; rc = select(fd + 1, NULL, NULL, &fds, &tm); if (rc != 1 || !FD_ISSET(fd, &fds)) exit(EXIT_FAILURE); rc = pread(fd, buf, sizeof(buf), 0); exit(EXIT_SUCCESS); } } rc = ndctl_cmd_submit(cmd); if (rc < 0) { fprintf(stderr, "%s: dimm: %#x failed to submit cmd: %d\n", __func__, ndctl_dimm_get_handle(dimm), rc); ndctl_cmd_unref(cmd); return rc; } /* * The same kernel change that adds nfit_test support for this * command is the same change that moves notifications to * require set_threshold. If we fail to get a command, but the * notification fires then we are on an old kernel, otherwise * whether old kernel or new kernel the notification should * fire. */ cmd_set = ndctl_dimm_cmd_new_smart_set_threshold(cmd); if (cmd_set) { /* * These values got reworked when nfit_test gained * set_threshold support */ __check_smart_threshold(dimm, cmd, media_temperature); __check_smart_threshold(dimm, cmd, ctrl_temperature); __check_smart_threshold(dimm, cmd, spares); __check_smart_threshold(dimm, cmd, alarm_control); /* * Set all thresholds to match current values and set * all alarms. */ rc = ndctl_cmd_smart_threshold_set_alarm_control(cmd_set, ndctl_cmd_smart_threshold_get_supported_alarms(cmd_set)); /* 'set_temperature' and 'set_media_temperature' are aliases */ rc |= ndctl_cmd_smart_threshold_set_temperature(cmd_set, ndctl_cmd_smart_get_media_temperature(cmd_smart)); rc |= ndctl_cmd_smart_threshold_set_ctrl_temperature(cmd_set, ndctl_cmd_smart_get_ctrl_temperature(cmd_smart)); rc |= ndctl_cmd_smart_threshold_set_spares(cmd_set, ndctl_cmd_smart_get_spares(cmd_smart)); if (rc) { fprintf(stderr, "%s: failed set threshold parameters\n", __func__); ndctl_cmd_unref(cmd_set); return -ENXIO; } rc = ndctl_cmd_submit(cmd_set); if (rc < 0) { fprintf(stderr, "%s: dimm: %#x failed to submit cmd_set: %d\n", __func__, ndctl_dimm_get_handle(dimm), rc); ndctl_cmd_unref(cmd_set); return rc; } ndctl_cmd_unref(cmd_set); } if (ndctl_test_attempt(check->test, KERNEL_VERSION(4, 9, 0))) { wait(&rc); if (WEXITSTATUS(rc) == EXIT_FAILURE) { fprintf(stderr, "%s: expect health event trigger\n", ndctl_dimm_get_devname(dimm)); return -ENXIO; } } ndctl_cmd_unref(cmd); return 0; } #define BITS_PER_LONG 32 static int check_commands(struct ndctl_bus *bus, struct ndctl_dimm *dimm, unsigned long bus_commands, unsigned long dimm_commands, struct ndctl_test *test) { /* * For now, by coincidence, these are indexed in test execution * order such that check_get_config_data can assume that * check_get_config_size has updated * check_cmd[ND_CMD_GET_CONFIG_SIZE].cmd and * check_set_config_data can assume that both * check_get_config_size and check_get_config_data have run */ struct check_cmd __check_dimm_cmds[] = { [ND_CMD_GET_CONFIG_SIZE] = { check_get_config_size }, [ND_CMD_GET_CONFIG_DATA] = { check_get_config_data }, [ND_CMD_SET_CONFIG_DATA] = { check_set_config_data }, [ND_CMD_SMART] = { check_smart }, [ND_CMD_SMART_THRESHOLD] = { .check_fn = check_smart_threshold, .test = test, }, }; unsigned int i, rc = 0; /* * The kernel did not start emulating v1.2 namespace spec smart data * until 4.9. */ if (!ndctl_test_attempt(test, KERNEL_VERSION(4, 9, 0))) dimm_commands &= ~((1 << ND_CMD_SMART) | (1 << ND_CMD_SMART_THRESHOLD)); /* Check DIMM commands */ check_cmds = __check_dimm_cmds; for (i = 0; i < BITS_PER_LONG; i++) { struct check_cmd *check = &check_cmds[i]; if ((dimm_commands & (1UL << i)) == 0) continue; if (!ndctl_dimm_is_cmd_supported(dimm, i)) { fprintf(stderr, "%s: bus: %s dimm%d expected cmd: %s supported\n", __func__, ndctl_bus_get_provider(bus), ndctl_dimm_get_id(dimm), ndctl_dimm_get_cmd_name(dimm, i)); rc = -ENXIO; break; } if (!check->check_fn) continue; rc = check->check_fn(bus, dimm, check); if (rc) break; } check_cmds = NULL; for (i = 0; i < ARRAY_SIZE(__check_dimm_cmds); i++) { if (__check_dimm_cmds[i].cmd) ndctl_cmd_unref(__check_dimm_cmds[i].cmd); __check_dimm_cmds[i].cmd = NULL; } if (rc) goto out; if (!ndctl_test_attempt(test, KERNEL_VERSION(4, 6, 0))) goto out; out: return rc; } static int check_dimms(struct ndctl_bus *bus, struct dimm *dimms, int n, unsigned long bus_commands, unsigned long dimm_commands, struct ndctl_test *test) { long long dsc; int i, j, rc; for (i = 0; i < n; i++) { struct ndctl_dimm *dimm = get_dimm_by_handle(bus, dimms[i].handle); if (!dimm) { fprintf(stderr, "failed to find dimm: %d\n", dimms[i].phys_id); return -ENXIO; } if (ndctl_dimm_get_phys_id(dimm) != dimms[i].phys_id) { fprintf(stderr, "dimm%d expected phys_id: %d got: %d\n", i, dimms[i].phys_id, ndctl_dimm_get_phys_id(dimm)); return -ENXIO; } if (ndctl_dimm_has_errors(dimm) != !!dimms[i].flags) { fprintf(stderr, "bus: %s dimm%d %s expected%s errors\n", ndctl_bus_get_provider(bus), i, ndctl_dimm_get_devname(dimm), dimms[i].flags ? "" : " no"); return -ENXIO; } if (ndctl_dimm_failed_save(dimm) != dimms[i].f_save || ndctl_dimm_failed_arm(dimm) != dimms[i].f_arm || ndctl_dimm_failed_restore(dimm) != dimms[i].f_restore || ndctl_dimm_smart_pending(dimm) != dimms[i].f_smart || ndctl_dimm_failed_flush(dimm) != dimms[i].f_flush) { fprintf(stderr, "expected: %s%s%s%s%sgot: %s%s%s%s%s\n", dimms[i].f_save ? "save_fail " : "", dimms[i].f_arm ? "not_armed " : "", dimms[i].f_restore ? "restore_fail " : "", dimms[i].f_smart ? "smart_event " : "", dimms[i].f_flush ? "flush_fail " : "", ndctl_dimm_failed_save(dimm) ? "save_fail " : "", ndctl_dimm_failed_arm(dimm) ? "not_armed " : "", ndctl_dimm_failed_restore(dimm) ? "restore_fail " : "", ndctl_dimm_smart_pending(dimm) ? "smart_event " : "", ndctl_dimm_failed_flush(dimm) ? "flush_fail " : ""); return -ENXIO; } if (ndctl_test_attempt(test, KERNEL_VERSION(4, 7, 0))) { if (ndctl_dimm_get_formats(dimm) != dimms[i].formats) { fprintf(stderr, "dimm%d expected formats: %d got: %d\n", i, dimms[i].formats, ndctl_dimm_get_formats(dimm)); fprintf(stderr, "continuing...\n"); } for (j = 0; j < dimms[i].formats; j++) { if (ndctl_dimm_get_formatN(dimm, j) != dimms[i].format[j]) { fprintf(stderr, "dimm%d expected format[%d]: %d got: %d\n", i, j, dimms[i].format[j], ndctl_dimm_get_formatN(dimm, j)); fprintf(stderr, "continuing...\n"); } } } if (ndctl_test_attempt(test, KERNEL_VERSION(4, 7, 0))) { if (ndctl_dimm_get_subsystem_vendor(dimm) != dimms[i].subsystem_vendor) { fprintf(stderr, "dimm%d expected subsystem vendor: %d got: %d\n", i, dimms[i].subsystem_vendor, ndctl_dimm_get_subsystem_vendor(dimm)); return -ENXIO; } } if (ndctl_test_attempt(test, KERNEL_VERSION(4, 8, 0))) { if (ndctl_dimm_get_manufacturing_date(dimm) != dimms[i].manufacturing_date) { fprintf(stderr, "dimm%d expected manufacturing date: %d got: %d\n", i, dimms[i].manufacturing_date, ndctl_dimm_get_manufacturing_date(dimm)); return -ENXIO; } } dsc = ndctl_dimm_get_dirty_shutdown(dimm); if (dsc != -ENOENT && dsc != dimms[i].dirty_shutdown) { fprintf(stderr, "dimm%d expected dirty shutdown: %lld got: %lld\n", i, dimms[i].dirty_shutdown, ndctl_dimm_get_dirty_shutdown(dimm)); return -ENXIO; } rc = check_commands(bus, dimm, bus_commands, dimm_commands, test); if (rc) return rc; } return 0; } enum dimm_reset { DIMM_INIT, DIMM_ZERO, }; static int reset_dimms(struct ndctl_bus *bus, enum dimm_reset reset) { struct ndctl_dimm *dimm; int rc = 0; ndctl_dimm_foreach(bus, dimm) { if (reset == DIMM_ZERO) ndctl_dimm_zero_labels(dimm); else { ndctl_dimm_read_label_index(dimm); ndctl_dimm_init_labels(dimm, NDCTL_NS_VERSION_1_2); } ndctl_dimm_disable(dimm); rc = ndctl_dimm_enable(dimm); if (rc) break; } return rc; } static void reset_bus(struct ndctl_bus *bus, enum dimm_reset reset) { struct ndctl_region *region; /* disable all regions so that set_config_data commands are permitted */ ndctl_region_foreach(bus, region) ndctl_region_disable_invalidate(region); reset_dimms(bus, reset); /* set regions back to their default state */ ndctl_region_foreach(bus, region) ndctl_region_enable(region); } static int do_test0(struct ndctl_ctx *ctx, struct ndctl_test *test) { struct ndctl_bus *bus = ndctl_bus_get_by_provider(ctx, NFIT_PROVIDER0); struct ndctl_region *region; int rc; if (!bus) return -ENXIO; ndctl_bus_wait_probe(bus); /* disable all regions so that set_config_data commands are permitted */ ndctl_region_foreach(bus, region) ndctl_region_disable_invalidate(region); rc = check_dimms(bus, dimms0, ARRAY_SIZE(dimms0), bus_commands0, dimm_commands0, test); if (rc) return rc; rc = reset_dimms(bus, DIMM_INIT); if (rc < 0) { fprintf(stderr, "failed to reset dimms\n"); return rc; } /* * Enable regions and adjust the space-align to drop the default * alignment constraints */ ndctl_region_foreach(bus, region) { ndctl_region_enable(region); ndctl_region_set_align(region, sysconf(_SC_PAGESIZE) * ndctl_region_get_interleave_ways(region)); } /* pfn and dax tests require vmalloc-enabled nfit_test */ if (ndctl_test_attempt(test, KERNEL_VERSION(4, 8, 0))) { rc = check_regions(bus, regions0, ARRAY_SIZE(regions0), DAX); if (rc) return rc; reset_bus(bus, DIMM_INIT); } if (ndctl_test_attempt(test, KERNEL_VERSION(4, 8, 0))) { rc = check_regions(bus, regions0, ARRAY_SIZE(regions0), PFN); if (rc) return rc; reset_bus(bus, DIMM_INIT); } return check_regions(bus, regions0, ARRAY_SIZE(regions0), BTT); } static int do_test1(struct ndctl_ctx *ctx, struct ndctl_test *test) { struct ndctl_bus *bus = ndctl_bus_get_by_provider(ctx, NFIT_PROVIDER1); int rc; if (!bus) return -ENXIO; ndctl_bus_wait_probe(bus); reset_bus(bus, DIMM_ZERO); /* * Starting with v4.10 the dimm on nfit_test.1 gets a unique * handle. */ if (ndctl_test_attempt(test, KERNEL_VERSION(4, 10, 0))) dimms1[0].handle = DIMM_HANDLE(1, 0, 0, 0, 0); rc = check_dimms(bus, dimms1, ARRAY_SIZE(dimms1), 0, 0, test); if (rc) return rc; return check_regions(bus, regions1, ARRAY_SIZE(regions1), BTT); } typedef int (*do_test_fn)(struct ndctl_ctx *ctx, struct ndctl_test *test); static do_test_fn do_test[] = { do_test0, do_test1, }; int test_libndctl(int loglevel, struct ndctl_test *test, struct ndctl_ctx *ctx) { unsigned int i; struct kmod_module *mod; struct kmod_ctx *kmod_ctx; struct daxctl_ctx *daxctl_ctx; int err, result = EXIT_FAILURE; if (!ndctl_test_attempt(test, KERNEL_VERSION(4, 2, 0))) return 77; ndctl_set_log_priority(ctx, loglevel); daxctl_ctx = ndctl_get_daxctl_ctx(ctx); daxctl_set_log_priority(daxctl_ctx, loglevel); ndctl_set_private_data(ctx, test); err = ndctl_test_init(&kmod_ctx, &mod, ctx, loglevel, test); if (err < 0) { ndctl_test_skip(test); fprintf(stderr, "nfit_test unavailable skipping tests\n"); return 77; } for (i = 0; i < ARRAY_SIZE(do_test); i++) { err = do_test[i](ctx, test); if (err < 0) { fprintf(stderr, "ndctl-test%d failed: %d\n", i, err); break; } } if (i >= ARRAY_SIZE(do_test)) result = EXIT_SUCCESS; kmod_module_remove_module(mod, 0); kmod_unref(kmod_ctx); return result; } int __attribute__((weak)) main(int argc, char *argv[]) { struct ndctl_test *test = ndctl_test_new(0); struct ndctl_ctx *ctx; int rc; if (!test) { fprintf(stderr, "failed to initialize test\n"); return EXIT_FAILURE; } rc = ndctl_new(&ctx); if (rc) return ndctl_test_result(test, rc); rc = test_libndctl(LOG_DEBUG, test, ctx); ndctl_unref(ctx); return ndctl_test_result(test, rc); } ndctl-81/test/list-smart-dimm.c000066400000000000000000000052241476737544500166070ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2018, FUJITSU LIMITED. All rights reserved. #include #include #include #include #include #include #include #include #include struct ndctl_filter_params param; static int did_fail; static int jflag = JSON_C_TO_STRING_PRETTY; #define fail(fmt, ...) \ do { \ did_fail = 1; \ fprintf(stderr, "ndctl-%s:%s:%d: " fmt, \ VERSION, __func__, __LINE__, ##__VA_ARGS__); \ } while (0) static bool filter_region(struct ndctl_region *region, struct ndctl_filter_ctx *ctx) { return true; } static void filter_dimm(struct ndctl_dimm *dimm, struct ndctl_filter_ctx *ctx) { struct list_filter_arg *lfa = ctx->list; struct json_object *jdimm; if (!ndctl_dimm_is_cmd_supported(dimm, ND_CMD_SMART)) return; if (!ndctl_dimm_is_cmd_supported(dimm, ND_CMD_SMART_THRESHOLD)) return; if (!ndctl_dimm_is_flag_supported(dimm, ND_SMART_ALARM_VALID)) return; if (!lfa->jdimms) { lfa->jdimms = json_object_new_array(); if (!lfa->jdimms) { fail("\n"); return; } } jdimm = util_dimm_to_json(dimm, lfa->flags); if (!jdimm) { fail("\n"); return; } json_object_array_add(lfa->jdimms, jdimm); } static bool filter_bus(struct ndctl_bus *bus, struct ndctl_filter_ctx *ctx) { return true; } static int list_display(struct list_filter_arg *lfa) { struct json_object *jdimms = lfa->jdimms; if (jdimms) util_display_json_array(stdout, jdimms, jflag); return 0; } int main(int argc, const char *argv[]) { struct ndctl_ctx *ctx; int i, rc; const struct option options[] = { OPT_STRING('b', "bus", ¶m.bus, "bus-id", "filter by bus"), OPT_STRING('r', "region", ¶m.region, "region-id", "filter by region"), OPT_STRING('d', "dimm", ¶m.dimm, "dimm-id", "filter by dimm"), OPT_STRING('n', "namespace", ¶m.namespace, "namespace-id", "filter by namespace id"), OPT_END(), }; const char * const u[] = { "list-smart-dimm []", NULL }; struct ndctl_filter_ctx fctx = { 0 }; struct list_filter_arg lfa = { 0 }; rc = ndctl_new(&ctx); if (rc < 0) return EXIT_FAILURE; argc = parse_options(argc, argv, options, u, 0); for (i = 0; i < argc; i++) error("unknown parameter \"%s\"\n", argv[i]); if (argc) usage_with_options(u, options); fctx.filter_bus = filter_bus; fctx.filter_dimm = filter_dimm; fctx.filter_region = filter_region; fctx.filter_namespace = NULL; fctx.list = &lfa; lfa.flags = 0; rc = ndctl_filter_walk(ctx, &fctx, ¶m); if (rc) return rc; if (list_display(&lfa) || did_fail) return -ENOMEM; return 0; } ndctl-81/test/max_available_extent_ns.sh000077500000000000000000000014241476737544500206310ustar00rootroot00000000000000#!/bin/bash -Ex # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2018, FUJITSU LIMITED. All rights reserved. rc=77 . $(dirname $0)/common trap 'err $LINENO' ERR check_min_kver "4.19" || do_skip "kernel $KVER may not support max_available_size" check_prereq "jq" do_test() { region=$($NDCTL list -b $NFIT_TEST_BUS0 -R -t pmem | jq -r 'sort_by(-.size) | .[].dev' | head -1) available_sz=$($NDCTL list -r $region | jq -r .[].available_size) size=$(( available_sz/4 )) NS=() for ((i=0; i<3; i++)) do NS[$i]=$($NDCTL create-namespace -r $region -t pmem -s $size | jq -r .dev) [[ -n ${NS[$i]} ]] done $NDCTL disable-namespace ${NS[1]} $NDCTL destroy-namespace ${NS[1]} $NDCTL create-namespace -r $region -t pmem } modprobe nfit_test rc=1 reset do_test _cleanup exit 0 ndctl-81/test/meson.build000066400000000000000000000175431476737544500155710ustar00rootroot00000000000000testcore = [ 'core.c', '../util/log.c', '../util/sysfs.c', ] libndctl_deps = [ ndctl_dep, daxctl_dep, uuid, kmod, ] ndctl_deps = libndctl_deps + [ json, util_dep, versiondep, ] libndctl = executable('libndctl', testcore + [ 'libndctl.c'], dependencies : libndctl_deps, include_directories : root_inc, ) namespace_core = [ '../ndctl/namespace.c', '../ndctl/filter.c', '../ndctl/check.c', '../util/json.c', '../ndctl/json.c', '../daxctl/filter.c', '../daxctl/json.c', ] dsm_fail = executable('dsm-fail', testcore + namespace_core + [ 'dsm-fail.c' ], dependencies : ndctl_deps, include_directories : root_inc, ) hugetlb_src = testcore + [ 'hugetlb.c', 'dax-pmd.c' ] if poison_enabled hugetlb_src += [ 'dax-poison.c' ] endif hugetlb = executable('hugetlb', hugetlb_src, dependencies : libndctl_deps, include_directories : root_inc, ) ack_shutdown_count = executable('ack-shutdown-count-set', testcore + [ 'ack-shutdown-count-set.c' ], dependencies : libndctl_deps, include_directories : root_inc, ) dax_errors = executable('dax-errors', 'dax-errors.c', ) smart_notify = executable('smart-notify', 'smart-notify.c', dependencies : libndctl_deps, include_directories : root_inc, ) smart_listen = executable('smart-listen', 'smart-listen.c', dependencies : libndctl_deps, include_directories : root_inc, ) daxdev_errors = executable('daxdev-errors', [ 'daxdev-errors.c', '../util/log.c', '../util/sysfs.c', ], dependencies : libndctl_deps, include_directories : root_inc, ) list_smart_dimm = executable('list-smart-dimm', [ 'list-smart-dimm.c', '../ndctl/filter.c', '../util/json.c', '../ndctl/json.c', '../daxctl/json.c', '../daxctl/filter.c', ], dependencies : ndctl_deps, include_directories : root_inc, ) pmem_ns = executable('pmem-ns', testcore + [ 'pmem_namespaces.c' ], dependencies : libndctl_deps, include_directories : root_inc, ) dax_dev = executable('dax-dev', testcore + [ 'dax-dev.c' ], dependencies : libndctl_deps, include_directories : root_inc, ) dax_pmd_src = testcore + [ 'dax-pmd.c' ] if poison_enabled dax_pmd_src += [ 'dax-poison.c' ] endif dax_pmd = executable('dax-pmd', dax_pmd_src, dependencies : libndctl_deps, include_directories : root_inc, ) device_dax_src = testcore + namespace_core + [ 'device-dax.c', 'dax-dev.c', 'dax-pmd.c', ] if poison_enabled device_dax_src += 'dax-poison.c' endif device_dax = executable('device-dax', device_dax_src, dependencies : ndctl_deps, include_directories : root_inc, ) revoke_devmem = executable('revoke_devmem', testcore + [ 'revoke-devmem.c', 'dax-dev.c', ], dependencies : libndctl_deps, include_directories : root_inc, ) mmap = executable('mmap', 'mmap.c',) create = find_program('create.sh') clear = find_program('clear.sh') pmem_errors = find_program('pmem-errors.sh') daxdev_errors_sh = find_program('daxdev-errors.sh') multi_dax = find_program('multi-dax.sh') btt_check = find_program('btt-check.sh') label_compat = find_program('label-compat.sh') sector_mode = find_program('sector-mode.sh') inject_error = find_program('inject-error.sh') btt_errors = find_program('btt-errors.sh') btt_pad_compat = find_program('btt-pad-compat.sh') firmware_update = find_program('firmware-update.sh') rescan_partitions = find_program('rescan-partitions.sh') inject_smart = find_program('inject-smart.sh') monitor = find_program('monitor.sh') max_extent = find_program('max_available_extent_ns.sh') pfn_meta_errors = find_program('pfn-meta-errors.sh') track_uuid = find_program('track-uuid.sh') cxl_topo = find_program('cxl-topology.sh') cxl_sysfs = find_program('cxl-region-sysfs.sh') cxl_labels = find_program('cxl-labels.sh') cxl_create_region = find_program('cxl-create-region.sh') cxl_xor_region = find_program('cxl-xor-region.sh') cxl_update_firmware = find_program('cxl-update-firmware.sh') cxl_events = find_program('cxl-events.sh') cxl_sanitize = find_program('cxl-sanitize.sh') cxl_destroy_region = find_program('cxl-destroy-region.sh') cxl_qos_class = find_program('cxl-qos-class.sh') cxl_poison = find_program('cxl-poison.sh') tests = [ [ 'libndctl', libndctl, 'ndctl' ], [ 'dsm-fail', dsm_fail, 'ndctl' ], [ 'create.sh', create, 'ndctl' ], [ 'clear.sh', clear, 'ndctl' ], [ 'pmem-errors.sh', pmem_errors, 'ndctl' ], [ 'daxdev-errors.sh', daxdev_errors_sh, 'dax' ], [ 'multi-dax.sh', multi_dax, 'dax' ], [ 'btt-check.sh', btt_check, 'ndctl' ], [ 'label-compat.sh', label_compat, 'ndctl' ], [ 'sector-mode.sh', sector_mode, 'ndctl' ], [ 'inject-error.sh', inject_error, 'ndctl' ], [ 'btt-errors.sh', btt_errors, 'ndctl' ], [ 'hugetlb', hugetlb, 'ndctl' ], [ 'btt-pad-compat.sh', btt_pad_compat, 'ndctl' ], [ 'ack-shutdown-count-set', ack_shutdown_count, 'ndctl' ], [ 'rescan-partitions.sh', rescan_partitions, 'ndctl' ], [ 'inject-smart.sh', inject_smart, 'ndctl' ], [ 'monitor.sh', monitor, 'ndctl' ], [ 'max_extent_ns', max_extent, 'ndctl' ], [ 'pfn-meta-errors.sh', pfn_meta_errors, 'ndctl' ], [ 'track-uuid.sh', track_uuid, 'ndctl' ], [ 'cxl-topology.sh', cxl_topo, 'cxl' ], [ 'cxl-region-sysfs.sh', cxl_sysfs, 'cxl' ], [ 'cxl-labels.sh', cxl_labels, 'cxl' ], [ 'cxl-create-region.sh', cxl_create_region, 'cxl' ], [ 'cxl-xor-region.sh', cxl_xor_region, 'cxl' ], [ 'cxl-events.sh', cxl_events, 'cxl' ], [ 'cxl-sanitize.sh', cxl_sanitize, 'cxl' ], [ 'cxl-destroy-region.sh', cxl_destroy_region, 'cxl' ], [ 'cxl-qos-class.sh', cxl_qos_class, 'cxl' ], [ 'cxl-poison.sh', cxl_poison, 'cxl' ], ] if get_option('destructive').enabled() sub_section = find_program('sub-section.sh') dax_ext4 = find_program('dax-ext4.sh') dax_xfs = find_program('dax-xfs.sh') align = find_program('align.sh') device_dax_fio = find_program('device-dax-fio.sh') daxctl_devices = find_program('daxctl-devices.sh') daxctl_create = find_program('daxctl-create.sh') dm = find_program('dm.sh') mmap_test = find_program('mmap.sh') tests += [ [ 'firmware-update.sh', firmware_update, 'ndctl' ], [ 'cxl-update-firmware.sh', cxl_update_firmware, 'cxl' ], [ 'pmem-ns', pmem_ns, 'ndctl' ], [ 'sub-section.sh', sub_section, 'dax' ], [ 'dax-dev', dax_dev, 'dax' ], [ 'dax-ext4.sh', dax_ext4, 'dax' ], [ 'dax-xfs.sh', dax_xfs, 'dax' ], [ 'align.sh', align, 'ndctl' ], [ 'device-dax', device_dax, 'dax' ], [ 'revoke-devmem', revoke_devmem, 'dax' ], [ 'device-dax-fio.sh', device_dax_fio, 'dax' ], [ 'daxctl-devices.sh', daxctl_devices, 'dax' ], [ 'daxctl-create.sh', daxctl_create, 'dax' ], [ 'dm.sh', dm, 'dax' ], [ 'mmap.sh', mmap_test, 'dax' ], ] endif if get_option('keyutils').enabled() nfit_security = find_program('nfit-security.sh') cxl_security = find_program('cxl-security.sh') tests += [ [ 'nfit-security.sh', nfit_security, 'ndctl' ], [ 'cxl-security.sh', cxl_security, 'cxl' ], ] endif foreach t : tests test(t[0], t[1], is_parallel : false, depends : [ ndctl_tool, daxctl_tool, cxl_tool, smart_notify, list_smart_dimm, dax_pmd, dax_errors, daxdev_errors, dax_dev, mmap, ], suite: t[2], timeout : 600, env : [ 'NDCTL=@0@'.format(ndctl_tool.full_path()), 'DAXCTL=@0@'.format(daxctl_tool.full_path()), 'TEST_PATH=@0@'.format(meson.current_build_dir()), 'DATA_PATH=@0@'.format(meson.current_source_dir()), ], ) endforeach ndctl-81/test/mmap.c000066400000000000000000000107601476737544500145170ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2015 Toshi Kani, Hewlett Packard Enterprise. All rights reserved. #include #include #include #include #include #include #include #include #include #define MiB(a) ((a) * 1024UL * 1024UL) static struct timeval start_tv, stop_tv; // Calculate the difference between two time values. static void tvsub(struct timeval *tdiff, struct timeval *t1, struct timeval *t0) { tdiff->tv_sec = t1->tv_sec - t0->tv_sec; tdiff->tv_usec = t1->tv_usec - t0->tv_usec; if (tdiff->tv_usec < 0) tdiff->tv_sec--, tdiff->tv_usec += 1000000; } // Start timing now. static void start(void) { (void) gettimeofday(&start_tv, (struct timezone *) 0); } // Stop timing and return real time in microseconds. static unsigned long long stop(void) { struct timeval tdiff; (void) gettimeofday(&stop_tv, (struct timezone *) 0); tvsub(&tdiff, &stop_tv, &start_tv); return (tdiff.tv_sec * 1000000 + tdiff.tv_usec); } static void test_write(unsigned long *p, size_t size) { size_t i; unsigned long *wp; unsigned long long timeval; start(); for (i=0, wp=p; i<(size/sizeof(*wp)); i++) *wp++ = 1; timeval = stop(); printf("Write: %10llu usec\n", timeval); } static void test_read(unsigned long *p, size_t size) { size_t i; volatile unsigned long *wp, tmp; unsigned long long timeval; start(); for (i=0, wp=p; i<(size/sizeof(*wp)); i++) tmp = *wp++; tmp = tmp; timeval = stop(); printf("Read : %10llu usec\n", timeval); } int main(int argc, char **argv) { int fd, i, opt, ret; int oflags, mprot, mflags = 0; int is_read_only = 0, is_mlock = 0, is_mlockall = 0; int mlock_skip = 0, read_test = 0, write_test = 0; void *mptr = NULL; unsigned long *p; struct stat stat; size_t size, cpy_size; const char *file_name = NULL; while ((opt = getopt(argc, argv, "RMSApsrw")) != -1) { switch (opt) { case 'R': printf("> mmap: read-only\n"); is_read_only = 1; break; case 'M': printf("> mlock\n"); is_mlock = 1; break; case 'S': printf("> mlock - skip first iteration\n"); mlock_skip = 1; break; case 'A': printf("> mlockall\n"); is_mlockall = 1; break; case 'p': printf("> MAP_POPULATE\n"); mflags |= MAP_POPULATE; break; case 's': printf("> MAP_SHARED\n"); mflags |= MAP_SHARED; break; case 'r': printf("> read-test\n"); read_test = 1; break; case 'w': printf("> write-test\n"); write_test = 1; break; } } if (optind == argc) { printf("missing file name\n"); return EXIT_FAILURE; } file_name = argv[optind]; if (!(mflags & MAP_SHARED)) { printf("> MAP_PRIVATE\n"); mflags |= MAP_PRIVATE; } if (is_read_only) { oflags = O_RDONLY; mprot = PROT_READ; } else { oflags = O_RDWR; mprot = PROT_READ|PROT_WRITE; } fd = open(file_name, oflags); if (fd == -1) { perror("open failed"); return EXIT_FAILURE; } ret = fstat(fd, &stat); if (ret < 0) { perror("fstat failed"); return EXIT_FAILURE; } size = stat.st_size; printf("> open %s size %#zx flags %#x\n", file_name, size, oflags); ret = posix_memalign(&mptr, MiB(2), size); if (ret ==0) free(mptr); printf("> mmap mprot 0x%x flags 0x%x\n", mprot, mflags); p = mmap(mptr, size, mprot, mflags, fd, 0x0); if (!p) { perror("mmap failed"); return EXIT_FAILURE; } if ((long unsigned)p & (MiB(2)-1)) printf("> mmap: NOT 2MiB aligned: 0x%p\n", p); else printf("> mmap: 2MiB aligned: 0x%p\n", p); cpy_size = size; for (i=0; i<3; i++) { if (is_mlock && !mlock_skip) { printf("> mlock 0x%p\n", p); ret = mlock(p, size); if (ret < 0) { perror("mlock failed"); return EXIT_FAILURE; } } else if (is_mlockall) { printf("> mlockall\n"); ret = mlockall(MCL_CURRENT|MCL_FUTURE); if (ret < 0) { perror("mlockall failed"); return EXIT_FAILURE; } } printf("===== %d =====\n", i+1); if (write_test) test_write(p, cpy_size); if (read_test) test_read(p, cpy_size); if (is_mlock && !mlock_skip) { printf("> munlock 0x%p\n", p); ret = munlock(p, size); if (ret < 0) { perror("munlock failed"); return EXIT_FAILURE; } } else if (is_mlockall) { printf("> munlockall\n"); ret = munlockall(); if (ret < 0) { perror("munlockall failed"); return EXIT_FAILURE; } } /* skip, if requested, only the first iteration */ mlock_skip = 0; } printf("> munmap 0x%p\n", p); munmap(p, size); return EXIT_SUCCESS; } ndctl-81/test/mmap.sh000077500000000000000000000035271476737544500147150ustar00rootroot00000000000000#!/bin/bash # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2015-2020 Intel Corporation. All rights reserved. . $(dirname $0)/common MNT=test_mmap_mnt FILE=image DEV="" TEST=$TEST_PATH/mmap rc=77 cleanup() { echo "test-mmap: failed at line $1" if [ -n "$DEV" ]; then umount $DEV else rc=77 fi rm -rf $MNT exit $rc } test_mmap() { # SHARED $TEST -Mrwps $MNT/$FILE # mlock, populate, shared (mlock fail) $TEST -Arwps $MNT/$FILE # mlockall, populate, shared $TEST -RMrps $MNT/$FILE # read-only, mlock, populate, shared (mlock fail) $TEST -rwps $MNT/$FILE # populate, shared (populate no effect) $TEST -Rrps $MNT/$FILE # read-only populate, shared (populate no effect) $TEST -Mrws $MNT/$FILE # mlock, shared (mlock fail) $TEST -RMrs $MNT/$FILE # read-only, mlock, shared (mlock fail) $TEST -rws $MNT/$FILE # shared (ok) $TEST -Rrs $MNT/$FILE # read-only, shared (ok) # PRIVATE $TEST -Mrwp $MNT/$FILE # mlock, populate, private (ok) $TEST -RMrp $MNT/$FILE # read-only, mlock, populate, private (mlock fail) $TEST -rwp $MNT/$FILE # populate, private (ok) $TEST -Rrp $MNT/$FILE # read-only, populate, private (populate no effect) $TEST -Mrw $MNT/$FILE # mlock, private (ok) $TEST -RMr $MNT/$FILE # read-only, mlock, private (mlock fail) $TEST -MSr $MNT/$FILE # private, read before mlock (ok) $TEST -rw $MNT/$FILE # private (ok) $TEST -Rr $MNT/$FILE # read-only, private (ok) } set -e mkdir -p $MNT trap 'err $LINENO cleanup' ERR dev=$($TEST_PATH/dax-dev) json=$($NDCTL list -N -n $dev) eval $(json2var <<< "$json") DEV="/dev/${blockdev}" rc=1 mkfs.ext4 $DEV mount $DEV $MNT -o dax fallocate -l 1GiB $MNT/$FILE test_mmap umount $MNT mkfs.xfs -f $DEV -m reflink=0 mount $DEV $MNT -o dax fallocate -l 1GiB $MNT/$FILE test_mmap umount $MNT ndctl-81/test/monitor.sh000077500000000000000000000071631476737544500154520ustar00rootroot00000000000000#!/bin/bash -Ex # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2018, FUJITSU LIMITED. All rights reserved. rc=77 monitor_pid=65536 logfile="" conf_file="" monitor_dimms="" monitor_regions="" monitor_namespace="" smart_supported_bus="" . $(dirname "$0")/common monitor_conf="$TEST_PATH/../ndctl" check_prereq "jq" trap 'err $LINENO' ERR check_min_kver "4.15" || do_skip "kernel $KVER may not support monitor service" start_monitor() { logfile=$(mktemp) $NDCTL monitor -c "$monitor_conf" -l "$logfile" $1 & monitor_pid=$! sync; sleep 3 truncate --size 0 "$logfile" #remove startup log } set_smart_supported_bus() { smart_supported_bus=$NFIT_TEST_BUS0 monitor_dimms=$("$TEST_PATH"/list-smart-dimm -b "$smart_supported_bus" | jq -r .[0].dev) if [ -z "$monitor_dimms" ]; then smart_supported_bus=$NFIT_TEST_BUS1 fi } get_monitor_dimm() { jlist=$("$TEST_PATH"/list-smart-dimm -b "$smart_supported_bus" $1) monitor_dimms=$(jq '.[]."dev"?, ."dev"?' <<<"$jlist" | sort | uniq | xargs) echo "$monitor_dimms" } call_notify() { "$TEST_PATH"/smart-notify "$smart_supported_bus" sync; sleep 3 } inject_smart() { $NDCTL inject-smart "$monitor_dimms" $1 sync; sleep 3 } check_result() { jlog=$(cat "$logfile") notify_dimms=$(jq ."dimm"."dev" <<<"$jlog" | sort | uniq | xargs) [[ "$1" == "$notify_dimms" ]] } stop_monitor() { kill $monitor_pid rm "$logfile" } test_filter_dimm() { monitor_dimms=$(get_monitor_dimm | awk '{print $1}') start_monitor "-d $monitor_dimms" call_notify check_result "$monitor_dimms" stop_monitor } test_filter_bus() { monitor_dimms=$(get_monitor_dimm) start_monitor "-b $smart_supported_bus" call_notify check_result "$monitor_dimms" stop_monitor } test_filter_region() { count=$($NDCTL list -R -b "$smart_supported_bus" | jq -r .[].dev | wc -l) i=0 while [ $i -lt "$count" ]; do monitor_region=$($NDCTL list -R -b "$smart_supported_bus" | jq -r .[$i].dev) monitor_dimms=$(get_monitor_dimm "-r $monitor_region") [[ "$monitor_dimms" ]] && break i=$((i + 1)) done start_monitor "-r $monitor_region" call_notify check_result "$monitor_dimms" stop_monitor } test_filter_namespace() { reset monitor_namespace=$($NDCTL create-namespace -b "$smart_supported_bus" | jq -r .dev) monitor_dimms=$(get_monitor_dimm "-n $monitor_namespace") start_monitor "-n $monitor_namespace" call_notify check_result "$monitor_dimms" stop_monitor $NDCTL destroy-namespace "$monitor_namespace" -f } test_conf_file() { monitor_dimms=$(get_monitor_dimm) conf_file=$(mktemp) echo -e "[monitor]\ndimm = $monitor_dimms" > "$conf_file" start_monitor "-c $conf_file" call_notify check_result "$monitor_dimms" stop_monitor rm "$conf_file" } test_filter_dimmevent() { monitor_dimms="$(get_monitor_dimm | awk '{print $1}')" start_monitor "-d $monitor_dimms -D dimm-unclean-shutdown" inject_smart "-U" check_result "$monitor_dimms" stop_monitor inject_value=$($NDCTL list -H -d "$monitor_dimms" | jq -r .[]."health"."spares_threshold") inject_value=$((inject_value - 1)) start_monitor "-d $monitor_dimms -D dimm-spares-remaining" inject_smart "-s $inject_value" check_result "$monitor_dimms" stop_monitor inject_value=$($NDCTL list -H -d "$monitor_dimms" | jq -r .[]."health"."temperature_threshold") inject_value=${inject_value%.*} inject_value=$((inject_value + 1)) start_monitor "-d $monitor_dimms -D dimm-media-temperature" inject_smart "-m $inject_value" check_result "$monitor_dimms" stop_monitor } do_tests() { test_filter_dimm test_filter_bus test_filter_region test_filter_namespace test_conf_file test_filter_dimmevent } modprobe nfit_test rc=1 reset set_smart_supported_bus do_tests _cleanup exit 0 ndctl-81/test/multi-dax.sh000077500000000000000000000016251476737544500156640ustar00rootroot00000000000000#!/bin/bash -x # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2015-2020 Intel Corporation. All rights reserved. set -e rc=77 . $(dirname $0)/common check_min_kver "4.13" || do_skip "may lack multi-dax support" check_prereq "jq" trap 'err $LINENO' ERR ALIGN_SIZE=`getconf PAGESIZE` # setup (reset nfit_test dimms) modprobe nfit_test reset rc=1 query=". | sort_by(.available_size) | reverse | .[0].dev" region=$($NDCTL list -b $NFIT_TEST_BUS0 -t pmem -Ri | jq -r "$query") json=$($NDCTL create-namespace -b $NFIT_TEST_BUS0 -r $region -t pmem -m devdax -a $ALIGN_SIZE -s 16M) chardev1=$(echo $json | jq ". | select(.mode == \"devdax\") | .daxregion.devices[0].chardev") json=$($NDCTL create-namespace -b $NFIT_TEST_BUS0 -r $region -t pmem -m devdax -a $ALIGN_SIZE -s 16M) chardev2=$(echo $json | jq ". | select(.mode == \"devdax\") | .daxregion.devices[0].chardev") check_dmesg "$LINENO" _cleanup exit 0 ndctl-81/test/nfit-security000066400000000000000000000023101476737544500161410ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2022, Intel Corp. All rights reserved. detect() { dev="$($NDCTL list -b "$NFIT_TEST_BUS0" -D | jq -r .[0].dev)" [ -n "$dev" ] || err "$LINENO" id="$($NDCTL list -b "$NFIT_TEST_BUS0" -D | jq -r .[0].id)" [ -n "$id" ] || err "$LINENO" } lock_dimm() { $NDCTL disable-dimm "$dev" # convert nmemX --> test_dimmY # For now this is the only user of such a conversion so we can leave it # inline. Once a subsequent user arrives we can refactor this to a # helper in test/common: # get_test_dimm_path "nfit_test.0" "nmem3" handle="$($NDCTL list -b "$NFIT_TEST_BUS0" -d "$dev" -i | jq -r .[].dimms[0].handle)" test_dimm_path="" for test_dimm in /sys/devices/platform/"$NFIT_TEST_BUS0"/nfit_test_dimm/test_dimm*; do td_handle_file="$test_dimm/handle" test -e "$td_handle_file" || continue td_handle="$(cat "$td_handle_file")" if [[ "$td_handle" -eq "$handle" ]]; then test_dimm_path="$test_dimm" break fi done test -d "$test_dimm_path" # now lock the dimm echo 1 > "${test_dimm_path}/lock_dimm" sstate="$(get_security_state)" if [ "$sstate" != "locked" ]; then echo "Incorrect security state: $sstate expected: locked" err "$LINENO" fi } ndctl-81/test/nfit-security.sh000077500000000000000000000002201476737544500165530ustar00rootroot00000000000000#!/bin/bash -Ex # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2022 Intel Corporation. All rights reserved. $(dirname $0)/security.sh nfit ndctl-81/test/nmem1.bin000066400000000000000000004000001476737544500151160ustar00rootroot00000000000000NAMESPACE_INDEXø>1بDcúÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿNAMESPACE_INDEXø>1Ø+Icúþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿh0ð9yB>ºÖ"½¨ýèÖûþ×öndctl-81/test/nmem2.bin000066400000000000000000004000001476737544500151170ustar00rootroot00000000000000NAMESPACE_INDEXø>1بDcúÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿNAMESPACE_INDEXø>1Ø+Icúþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿh0ð9yB>ºÖ"½¨ýèÖûþ×öndctl-81/test/nmem3.bin000066400000000000000000004000001476737544500151200ustar00rootroot00000000000000NAMESPACE_INDEXø>1بDcúÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿNAMESPACE_INDEXø>1Ø+Icúþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿh0ð9yB>ºÖ"½¨ýèÖûþ×öndctl-81/test/nmem4.bin000066400000000000000000004000001476737544500151210ustar00rootroot00000000000000NAMESPACE_INDEXø>1بDcúÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿNAMESPACE_INDEXø>1Ø+Icúþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿh0ð9yB>ºÖ"½¨ýèÖûþ×öndctl-81/test/pfn-meta-errors.sh000077500000000000000000000031661476737544500170030ustar00rootroot00000000000000#!/bin/bash -Ex # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2018-2020 Intel Corporation. All rights reserved. blockdev="" rc=77 . $(dirname $0)/common force_raw() { raw="$1" $NDCTL disable-namespace "$dev" echo "$raw" > "/sys/bus/nd/devices/$dev/force_raw" $NDCTL enable-namespace "$dev" echo "Set $dev to raw mode: $raw" if [[ "$raw" == "1" ]]; then raw_bdev=${blockdev} test -b "/dev/$raw_bdev" else raw_bdev="" fi } check_min_kver "4.20" || do_skip "may lack PFN metadata error handling" set -e trap 'err $LINENO' ERR # setup (reset nfit_test dimms) modprobe nfit_test reset rc=1 # create a fsdax namespace and clear errors (if any) dev="x" json=$($NDCTL create-namespace -b $NFIT_TEST_BUS0 -t pmem -m fsdax) eval "$(echo "$json" | json2var)" [ $dev = "x" ] && echo "fail: $LINENO" && exit 1 force_raw 1 if read -r sector len < "/sys/block/$raw_bdev/badblocks"; then dd of=/dev/$raw_bdev if=/dev/zero oflag=direct bs=512 seek="$sector" count="$len" fi force_raw 0 # find dataoff from sb force_raw 1 doff=$(hexdump -s $((4096 + 56)) -n 4 "/dev/$raw_bdev" | head -1 | cut -d' ' -f2-) doff=$(tr -d ' ' <<< "0x${doff#* }${doff%% *}") printf "pfn dataoff: %x\n" "$doff" dblk="$((doff/512))" metaoff="0x2000" mblk="$((metaoff/512))" # inject in the middle of the struct page area bb_inj=$(((dblk - mblk)/2)) $NDCTL inject-error --block="$bb_inj" --count=32 $dev $NDCTL start-scrub $NFIT_TEST_BUS0 && $NDCTL wait-scrub $NFIT_TEST_BUS0 # after probe from the enable-namespace, the error should've been cleared force_raw 0 force_raw 1 if read -r sector len < "/sys/block/$raw_bdev/badblocks"; then false fi _cleanup exit 0 ndctl-81/test/pmem-errors.sh000077500000000000000000000065511476737544500162330ustar00rootroot00000000000000#!/bin/bash -x # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2015-2020 Intel Corporation. All rights reserved. MNT=test_dax_mnt FILE=image rc=77 . $(dirname $0)/common cleanup() { if [ -n "$blockdev" ]; then umount /dev/$blockdev else rc=77 fi rm -rf $MNT } check_min_kver "4.7" || do_skip "may lack dax error handling" set -e mkdir -p $MNT trap 'err $LINENO cleanup' ERR # setup (reset nfit_test dimms) modprobe nfit_test reset rc=1 # create pmem dev="x" json=$($NDCTL create-namespace -b $NFIT_TEST_BUS0 -t pmem -m raw) eval $(echo $json | json2var) [ $dev = "x" ] && echo "fail: $LINENO" && false [ $mode != "raw" ] && echo "fail: $LINENO" && false # inject errors in the middle of the namespace, verify that reading fails err_sector="$(((size/512) / 2))" err_count=8 if ! read sector len < /sys/block/$blockdev/badblocks; then $NDCTL inject-error --block="$err_sector" --count=$err_count $dev $NDCTL start-scrub $NFIT_TEST_BUS0; $NDCTL wait-scrub $NFIT_TEST_BUS0 fi read sector len < /sys/block/$blockdev/badblocks [ $((sector * 2)) -ne $((size /512)) ] && echo "fail: $LINENO" && false if dd if=/dev/$blockdev of=/dev/null iflag=direct bs=512 skip=$sector count=$len; then echo "fail: $LINENO" && false fi # check that writing clears the errors if ! dd of=/dev/$blockdev if=/dev/zero oflag=direct bs=512 seek=$sector count=$len; then echo "fail: $LINENO" && false fi if read sector len < /sys/block/$blockdev/badblocks; then # fail if reading badblocks returns data echo "fail: $LINENO" && false fi #mkfs.xfs /dev/$blockdev -b size=4096 -f mkfs.ext4 /dev/$blockdev -b 4096 mount /dev/$blockdev $MNT # prepare an image file with random data dd if=/dev/urandom of=$MNT/$FILE bs=4096 count=4 oflag=direct # Get the start sector for the file start_sect=$(filefrag -v -b512 $MNT/$FILE | grep -E "^[ ]+[0-9]+.*" | head -1 | awk '{ print $4 }' | cut -d. -f1) test -n "$start_sect" echo "start sector of the file is $start_sect" # inject badblocks for one page at the start of the file echo $start_sect 8 > /sys/block/$blockdev/badblocks # make sure reading the first block of the file fails as expected : The following 'dd' is expected to hit an I/O Error dd if=$MNT/$FILE of=/dev/null iflag=direct bs=4096 count=1 && err $LINENO || true # run the dax-errors test test -x $TEST_PATH/dax-errors $TEST_PATH/dax-errors $MNT/$FILE # TODO: disable this check till we have clear-on-write in the kernel #if read sector len < /sys/block/$blockdev/badblocks; then # # fail if reading badblocks returns data # echo "fail: $LINENO" && false #fi # TODO Due to the above, we have to clear the existing badblock manually read sector len < /sys/block/$blockdev/badblocks if ! dd of=/dev/$blockdev if=/dev/zero oflag=direct bs=512 seek=$sector count=$len; then echo "fail: $LINENO" && false fi # test that a hole punch to a dax file also clears errors dd if=/dev/urandom of=$MNT/$FILE oflag=direct bs=4096 count=4 start_sect=$(filefrag -v -b512 $MNT/$FILE | grep -E "^[ ]+[0-9]+.*" | head -1 | awk '{ print $4 }' | cut -d. -f1) test -n "$start_sect" echo "holepunch test: start sector: $start_sect" # inject a badblock at the second sector of the first page echo $((start_sect + 1)) 1 > /sys/block/$blockdev/badblocks # verify badblock by reading : The following 'dd' is expected to hit an I/O Error dd if=$MNT/$FILE of=/dev/null iflag=direct bs=4096 count=1 && err $LINENO || true cleanup _cleanup exit 0 ndctl-81/test/pmem_namespaces.c000066400000000000000000000143331476737544500167220ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-2.1 // Copyright (C) 2015-2020, Intel Corporation. All rights reserved. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define err(msg)\ fprintf(stderr, "%s:%d: %s (%s)\n", __func__, __LINE__, msg, strerror(errno)) static struct ndctl_namespace *create_pmem_namespace(struct ndctl_region *region) { struct ndctl_namespace *seed_ns = NULL; unsigned long long size; uuid_t uuid; seed_ns = ndctl_region_get_namespace_seed(region); if (!seed_ns) return NULL; uuid_generate(uuid); size = ndctl_region_get_size(region); if (ndctl_namespace_set_uuid(seed_ns, uuid) < 0) return NULL; if (ndctl_namespace_set_size(seed_ns, size) < 0) return NULL; if (ndctl_namespace_enable(seed_ns) < 0) return NULL; return seed_ns; } static int disable_pmem_namespace(struct ndctl_namespace *ndns) { if (ndctl_namespace_disable_invalidate(ndns) < 0) return -ENODEV; if (ndctl_namespace_delete(ndns) < 0) return -ENODEV; return 0; } static int ns_do_io(const char *bdev) { unsigned long num_dev_pages, num_blocks; const int page_size = 4096; const int num_pages = 2; off_t addr; int rc = 0; int fd, i; void *random_page[num_pages]; void *pmem_page[num_pages]; rc = posix_memalign(random_page, page_size, page_size * num_pages); if (rc) { fprintf(stderr, "posix_memalign failure\n"); return rc; } rc = posix_memalign(pmem_page, page_size, page_size * num_pages); if (rc) { fprintf(stderr, "posix_memalign failure\n"); goto err_free_pmem; } for (i = 1; i < num_pages; i++) { random_page[i] = (char*)random_page[0] + page_size * i; pmem_page[i] = (char*)pmem_page[0] + page_size * i; } /* read random data into random_page */ if ((fd = open("/dev/urandom", O_RDONLY)) < 0) { err("open"); rc = -ENODEV; goto err_free_all; } rc = read(fd, random_page[0], page_size * num_pages); if (rc < 0) { err("read"); close(fd); goto err_free_all; } close(fd); /* figure out our dev size */ if ((fd = open(bdev, O_RDWR|O_DIRECT)) < 0) { err("open"); rc = -ENODEV; goto err_free_all; } ioctl(fd, BLKGETSIZE, &num_blocks); num_dev_pages = num_blocks / 8; /* write the random data out to each of the segments */ rc = pwrite(fd, random_page[0], page_size, 0); if (rc < 0) { err("write"); goto err_close; } addr = page_size * (num_dev_pages - 1); rc = pwrite(fd, random_page[1], page_size, addr); if (rc < 0) { err("write"); goto err_close; } /* read back the random data into pmem_page */ rc = pread(fd, pmem_page[0], page_size, 0); if (rc < 0) { err("read"); goto err_close; } addr = page_size * (num_dev_pages - 1); rc = pread(fd, pmem_page[1], page_size, addr); if (rc < 0) { err("read"); goto err_close; } /* verify the data */ if (memcmp(random_page[0], pmem_page[0], page_size * num_pages)) { fprintf(stderr, "PMEM data miscompare\n"); rc = -EIO; goto err_close; } rc = 0; err_close: close(fd); err_free_all: free(random_page[0]); err_free_pmem: free(pmem_page[0]); return rc; } static const char *comm = "test-pmem-namespaces"; int test_pmem_namespaces(int log_level, struct ndctl_test *test, struct ndctl_ctx *ctx) { struct ndctl_region *region, *pmem_region = NULL; struct kmod_ctx *kmod_ctx = NULL; struct kmod_module *mod = NULL; struct ndctl_namespace *ndns; struct ndctl_dimm *dimm; struct ndctl_bus *bus; int rc = -ENXIO; char bdev[50]; if (!ndctl_test_attempt(test, KERNEL_VERSION(4, 2, 0))) return 77; ndctl_set_log_priority(ctx, log_level); bus = ndctl_bus_get_by_provider(ctx, "ACPI.NFIT"); if (bus) { /* skip this bus if no label-enabled PMEM regions */ ndctl_region_foreach(bus, region) if (ndctl_region_get_nstype(region) == ND_DEVICE_NAMESPACE_PMEM) break; if (!region) bus = NULL; } if (!bus) { fprintf(stderr, "ACPI.NFIT unavailable falling back to nfit_test\n"); rc = ndctl_test_init(&kmod_ctx, &mod, NULL, log_level, test); ndctl_invalidate(ctx); bus = ndctl_bus_get_by_provider(ctx, "nfit_test.0"); if (rc < 0 || !bus) { rc = 77; ndctl_test_skip(test); fprintf(stderr, "nfit_test unavailable skipping tests\n"); goto err_module; } } fprintf(stderr, "%s: found provider: %s\n", comm, ndctl_bus_get_provider(bus)); /* get the system to a clean state */ ndctl_region_foreach(bus, region) ndctl_region_disable_invalidate(region); ndctl_dimm_foreach(bus, dimm) { rc = ndctl_dimm_zero_labels(dimm); if (rc < 0) { fprintf(stderr, "failed to zero %s\n", ndctl_dimm_get_devname(dimm)); goto err; } } /* create our config */ ndctl_region_foreach(bus, region) if (strcmp(ndctl_region_get_type_name(region), "pmem") == 0) { pmem_region = region; break; } if (!pmem_region || ndctl_region_enable(pmem_region) < 0) { fprintf(stderr, "%s: failed to find PMEM region\n", comm); rc = -ENODEV; goto err; } rc = -ENODEV; ndns = create_pmem_namespace(pmem_region); if (!ndns) { fprintf(stderr, "%s: failed to create PMEM namespace\n", comm); goto err; } sprintf(bdev, "/dev/%s", ndctl_namespace_get_block_device(ndns)); rc = ns_do_io(bdev); disable_pmem_namespace(ndns); err: /* unload nfit_test */ bus = ndctl_bus_get_by_provider(ctx, "nfit_test.0"); if (bus) ndctl_region_foreach(bus, region) ndctl_region_disable_invalidate(region); bus = ndctl_bus_get_by_provider(ctx, "nfit_test.1"); if (bus) ndctl_region_foreach(bus, region) ndctl_region_disable_invalidate(region); kmod_module_remove_module(mod, 0); err_module: kmod_unref(kmod_ctx); return rc; } int __attribute__((weak)) main(int argc, char *argv[]) { struct ndctl_test *test = ndctl_test_new(0); struct ndctl_ctx *ctx; int rc; comm = argv[0]; if (!test) { fprintf(stderr, "failed to initialize test\n"); return EXIT_FAILURE; } rc = ndctl_new(&ctx); if (rc) return ndctl_test_result(test, rc); rc = test_pmem_namespaces(LOG_DEBUG, test, ctx); ndctl_unref(ctx); return ndctl_test_result(test, rc); } ndctl-81/test/rescan-partitions.sh000077500000000000000000000031141476737544500174200ustar00rootroot00000000000000#!/bin/bash -Ex # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2018-2020 Intel Corporation. All rights reserved. dev="" size="" blockdev="" rc=77 . $(dirname $0)/common trap 'err $LINENO' ERR # sample json: #{ # "dev":"namespace5.0", # "mode":"sector", # "size":"60.00 MiB (62.92 MB)", # "uuid":"f1baa71a-d165-4da4-bb6a-083a2b0e6469", # "blockdev":"pmem5s", #} check_min_kver "4.16" || do_skip "may not contain fixes for partition rescanning" check_prereq "parted" check_prereq "blockdev" check_prereq "jq" test_mode() { local mode="$1" # create namespace json=$($NDCTL create-namespace -b $NFIT_TEST_BUS0 -t pmem -m "$mode") rc=2 eval "$(echo "$json" | json2var)" [ -n "$dev" ] || err "$LINENO" [ -n "$size" ] || err "$LINENO" [ -n "$blockdev" ] || err "$LINENO" [ $size -gt 0 ] || err "$LINENO" rc=1 # create partition parted --script /dev/$blockdev mklabel gpt mkpart primary 1MiB 10MiB # verify it is created sleep 1 blockdev --rereadpt /dev/$blockdev sleep 1 partdev=$(lsblk -J -o NAME,SIZE /dev/$blockdev | jq -r '.blockdevices[] | .children[0] | .name') test -b /dev/$partdev # cycle the namespace, and verify the partition is read # without needing to do a blockdev --rereadpt $NDCTL disable-namespace $dev $NDCTL enable-namespace $dev if [ -b /dev/$partdev ]; then echo "mode: $mode - partition read successful" else echo "mode: $mode - partition read failed" rc=1 err "$LINENO" fi $NDCTL disable-namespace $dev $NDCTL destroy-namespace $dev } modprobe nfit_test rc=1 reset test_mode "raw" test_mode "fsdax" test_mode "sector" _cleanup exit 0 ndctl-81/test/revoke-devmem.c000066400000000000000000000060201476737544500163250ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0-only /* Copyright (C) 2020 Intel Corporation. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static sigjmp_buf sj_env; static void sigbus(int sig, siginfo_t *siginfo, void *d) { siglongjmp(sj_env, 1); } #define err(fmt, ...) \ fprintf(stderr, "%s: " fmt, __func__, ##__VA_ARGS__) static int test_devmem(int loglevel, struct ndctl_test *test, struct ndctl_ctx *ctx) { void *buf; int fd, rc; struct sigaction act; unsigned long long resource; struct ndctl_namespace *ndns; ndctl_set_log_priority(ctx, loglevel); /* iostrict devmem started in kernel 4.5 */ if (!ndctl_test_attempt(test, KERNEL_VERSION(4, 5, 0))) return 77; ndns = ndctl_get_test_dev(ctx); if (!ndns) { err("failed to find suitable namespace\n"); return 77; } resource = ndctl_namespace_get_resource(ndns); if (resource == ULLONG_MAX) { err("failed to retrieve resource base\n"); return 77; } rc = ndctl_namespace_disable(ndns); if (rc) { err("failed to disable namespace\n"); return rc; } /* establish a devmem mapping of the namespace memory */ fd = open("/dev/mem", O_RDWR); if (fd < 0) { err("failed to open /dev/mem: %s\n", strerror(errno)); rc = -errno; goto out_devmem; } buf = mmap(NULL, SZ_2M, PROT_READ|PROT_WRITE, MAP_SHARED, fd, resource); if (buf == MAP_FAILED) { err("failed to map /dev/mem: %s\n", strerror(errno)); rc = -errno; goto out_mmap; } /* populate and write, should not fail */ memset(buf, 0, SZ_2M); memset(&act, 0, sizeof(act)); act.sa_sigaction = sigbus; act.sa_flags = SA_SIGINFO; if (sigaction(SIGBUS, &act, 0)) { perror("sigaction"); rc = EXIT_FAILURE; goto out_sigaction; } /* test fault due to devmem revocation */ if (sigsetjmp(sj_env, 1)) { /* got sigbus, success */ fprintf(stderr, "devmem revoked!\n"); rc = 0; goto out_sigaction; } rc = ndctl_namespace_enable(ndns); if (rc) { err("failed to enable namespace\n"); goto out_sigaction; } /* write, should sigbus */ memset(buf, 0, SZ_2M); err("kernel failed to prevent write after namespace enabled\n"); rc = -ENXIO; out_sigaction: munmap(buf, SZ_2M); out_mmap: close(fd); out_devmem: if (ndctl_namespace_enable(ndns) != 0) err("failed to re-enable namespace\n"); return rc; } int main(int argc, char *argv[]) { struct ndctl_test *test = ndctl_test_new(0); struct ndctl_ctx *ctx; int rc; if (!test) { fprintf(stderr, "failed to initialize test\n"); return EXIT_FAILURE; } rc = ndctl_new(&ctx); if (rc < 0) return ndctl_test_result(test, rc); rc = test_devmem(LOG_DEBUG, test, ctx); ndctl_unref(ctx); return ndctl_test_result(test, rc); } ndctl-81/test/sector-mode.sh000077500000000000000000000014171476737544500162000ustar00rootroot00000000000000#!/bin/bash -x # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2015-2020 Intel Corporation. All rights reserved. rc=77 . $(dirname $0)/common check_prereq "jq" set -e trap 'err $LINENO' ERR ALIGN_SIZE=`getconf PAGESIZE` # setup (reset nfit_test dimms) modprobe nfit_test reset reset1 rc=1 query=". | sort_by(.available_size) | reverse | .[0].dev" REGION=$($NDCTL list -R -b $NFIT_TEST_BUS1 | jq -r "$query") echo 0 > /sys/bus/nd/devices/$REGION/read_only echo $ALIGN_SIZE > /sys/bus/nd/devices/$REGION/align NAMESPACE=$($NDCTL create-namespace --no-autolabel -r $REGION -m sector -f -l 4K | jq -r ".dev") $NDCTL create-namespace --no-autolabel -e $NAMESPACE -m dax -f -a $ALIGN_SIZE $NDCTL create-namespace --no-autolabel -e $NAMESPACE -m sector -f -l 4K _cleanup exit 0 ndctl-81/test/security.sh000077500000000000000000000131361476737544500156270ustar00rootroot00000000000000#!/bin/bash -Ex # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2018-2020 Intel Corporation. All rights reserved. rc=77 dev="" id="" keypath="/etc/ndctl/keys" masterkey="nvdimm-master" masterpath="$keypath/$masterkey.blob" backup_key=0 backup_handle=0 . $(dirname $0)/common trap 'err $LINENO' ERR setup() { $NDCTL disable-region -b "$TEST_BUS" all } setup_keys() { if [ ! -d "$keypath" ]; then mkdir -p "$keypath" fi if [ -f "$masterpath" ]; then mv "$masterpath" "$masterpath.bak" backup_key=1 fi if [ -f "$keypath/tpm.handle" ]; then mv "$keypath/tpm.handle" "$keypath/tpm.handle.bak" backup_handle=1 fi # Make sure there is a session and a user keyring linked into it keyctl new_session keyctl link @u @s dd if=/dev/urandom bs=1 count=32 2>/dev/null | keyctl padd user "$masterkey" @u keyctl pipe "$(keyctl search @u user $masterkey)" > "$masterpath" } test_cleanup() { if keyctl search @u encrypted nvdimm:"$id"; then keyctl unlink "$(keyctl search @u encrypted nvdimm:"$id")" fi if keyctl search @u user "$masterkey"; then keyctl unlink "$(keyctl search @u user "$masterkey")" fi if [ -f "$keypath"/nvdimm_"$id"_"$(hostname)".blob ]; then rm -f "$keypath"/nvdimm_"$id"_"$(hostname)".blob fi } post_cleanup() { if [ -f $masterpath ]; then rm -f "$masterpath" fi if [ "$backup_key" -eq 1 ]; then mv "$masterpath.bak" "$masterpath" fi if [ "$backup_handle" -eq 1 ]; then mv "$keypath/tpm.handle.bak" "$keypath/tpm.handle" fi } get_frozen_state() { $NDCTL list -i -b "$TEST_BUS" -d "$dev" | jq -r .[].dimms[0].security_frozen } get_security_state() { $NDCTL list -i -b "$TEST_BUS" -d "$dev" | jq -r .[].dimms[0].security } setup_passphrase() { $NDCTL setup-passphrase "$dev" -k user:"$masterkey" sstate="$(get_security_state)" if [ "$sstate" != "unlocked" ]; then echo "Incorrect security state: $sstate expected: unlocked" err "$LINENO" fi } remove_passphrase() { $NDCTL remove-passphrase "$dev" sstate="$(get_security_state)" if [ "$sstate" != "disabled" ]; then echo "Incorrect security state: $sstate expected: disabled" err "$LINENO" fi } erase_security() { $NDCTL sanitize-dimm -c "$dev" sstate="$(get_security_state)" if [ "$sstate" != "disabled" ]; then echo "Incorrect security state: $sstate expected: disabled" err "$LINENO" fi } update_security() { $NDCTL update-passphrase "$dev" sstate="$(get_security_state)" if [ "$sstate" != "unlocked" ]; then echo "Incorrect security state: $sstate expected: unlocked" err "$LINENO" fi } freeze_security() { $NDCTL freeze-security "$dev" } test_1_security_setup_and_remove() { setup_passphrase remove_passphrase } test_2_security_setup_and_update() { setup_passphrase update_security remove_passphrase } test_3_security_setup_and_erase() { setup_passphrase erase_security } test_4_security_unlock() { setup_passphrase lock_dimm $NDCTL enable-dimm "$dev" sstate="$(get_security_state)" if [ "$sstate" != "unlocked" ]; then echo "Incorrect security state: $sstate expected: unlocked" err "$LINENO" fi $NDCTL disable-region -b "$TEST_BUS" all remove_passphrase } # This should always be the last nvdimm security test. # with security frozen, nfit_test must be removed and is no longer usable test_5_security_freeze() { setup_passphrase freeze_security sstate="$(get_security_state)" fstate="$(get_frozen_state)" if [ "$fstate" != "true" ]; then echo "Incorrect security state: expected: frozen" err "$LINENO" fi $NDCTL remove-passphrase "$dev" && { echo "remove succeed after frozen"; } sstate2="$(get_security_state)" if [ "$sstate" != "$sstate2" ]; then echo "Incorrect security state: $sstate2 expected: $sstate" err "$LINENO" fi } test_6_load_keys() { if keyctl search @u encrypted nvdimm:"$id"; then keyctl unlink "$(keyctl search @u encrypted nvdimm:"$id")" fi if keyctl search @u user "$masterkey"; then keyctl unlink "$(keyctl search @u user "$masterkey")" fi $NDCTL load-keys if keyctl search @u user "$masterkey"; then echo "master key loaded" else echo "master key failed to loaded" err "$LINENO" fi if keyctl search @u encrypted nvdimm:"$id"; then echo "dimm key loaded" else echo "dimm key failed to load" err "$LINENO" fi } if [ "$1" = "nfit" ]; then . $(dirname $0)/nfit-security TEST_BUS="$NFIT_TEST_BUS0" check_min_kver "5.0" || do_skip "may lack security handling" KMOD_TEST="nfit_test" elif [ "$1" = "cxl" ]; then . $(dirname $0)/cxl-security TEST_BUS="$CXL_TEST_BUS" check_min_kver "6.2" || do_skip "may lack security handling" KMOD_TEST="cxl_test" else do_skip "Missing input parameters" fi check_prereq "keyctl" check_prereq "jq" uid="$(keyctl show | grep -Eo "_uid.[0-9]+" | head -1 | cut -d. -f2-)" if [ "$uid" -ne 0 ]; then do_skip "run as root or with a sudo login shell for test to work" fi modprobe "$KMOD_TEST" $CXL list setup rc=1 detect test_cleanup setup_keys echo "Test 1, security setup and remove" test_1_security_setup_and_remove echo "Test 2, security setup, update, and remove" test_2_security_setup_and_update echo "Test 3, security setup and erase" test_3_security_setup_and_erase echo "Test 4, unlock dimm" test_4_security_unlock # Freeze should always be the last nvdimm security test because it locks # security state and require nfit_test module unload. However, this does # not impact any key management testing via libkeyctl. echo "Test 5, freeze security" test_5_security_freeze # Load-keys is independent of actual nvdimm security and is part of key # mangement testing. echo "Test 6, test load-keys" test_6_load_keys test_cleanup post_cleanup if [ "$1" = "nfit" ]; then _cleanup elif [ "$1" = "cxl" ]; then _cxl_cleanup fi exit 0 ndctl-81/test/smart-listen.c000066400000000000000000000040371476737544500162070ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2017-2020 Intel Corporation. All rights reserved. #include #include #include #include #include #include static void dimm_listen(struct ndctl_bus *bus) { struct ndctl_dimm *dimm, **dimms; int count = 0, maxfd = -1, i, rc; struct pollfd *poll_ents, *p; char buf; ndctl_dimm_foreach(bus, dimm) { int fd = ndctl_dimm_get_health_eventfd(dimm); if (fd > maxfd) maxfd = fd; count++; } if (!count) { fprintf(stderr, "no dimms on bus: %s\n", ndctl_bus_get_provider(bus)); return; } poll_ents = calloc(count, sizeof(struct pollfd)); dimms = calloc(maxfd + 1, sizeof(struct ndctl_dimm *)); if (!poll_ents) goto out; if (!dimms) goto out; i = 0; ndctl_dimm_foreach(bus, dimm) { int fd = ndctl_dimm_get_health_eventfd(dimm); p = &poll_ents[i++]; p->fd = fd; dimms[fd] = dimm; if (i > count) { fprintf(stderr, "dimm count changed!?\n"); goto out; } } retry: for (i = 0; i < count; i++) { p = &poll_ents[i]; dimm = dimms[p->fd]; if (pread(p->fd, &buf, 1, 0) != 1) { fprintf(stderr, "%s: failed to read\n", ndctl_dimm_get_devname(dimm)); goto out; } if (p->revents) fprintf(stderr, "%s: smart event: %d\n", ndctl_dimm_get_devname(dimm), p->revents); p->revents = 0; } rc = poll(poll_ents, count, -1); if (rc <= 0) { fprintf(stderr, "failed to poll\n"); goto out; } goto retry; out: free(poll_ents); free(dimms); } int main(int argc, char *argv[]) { struct ndctl_ctx *ctx; struct ndctl_bus *bus; int rc = EXIT_FAILURE; const char *provider; rc = ndctl_new(&ctx); if (rc < 0) return EXIT_FAILURE; if (argc != 2) { fprintf(stderr, "usage: smart-notify \n"); goto out; } provider = argv[1]; bus = ndctl_bus_get_by_provider(ctx, provider); if (!bus) { fprintf(stderr, "smart-notify: unable to find bus (%s)\n", provider); goto out; } rc = EXIT_SUCCESS; dimm_listen(bus); out: ndctl_unref(ctx); return rc; } ndctl-81/test/smart-notify.c000066400000000000000000000134051476737544500162200ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2017-2020 Intel Corporation. All rights reserved. #include #include #include #include #include #include static void do_notify(struct ndctl_dimm *dimm) { struct ndctl_cmd *s_cmd = ndctl_dimm_cmd_new_smart(dimm); struct ndctl_cmd *st_cmd = NULL, *sst_cmd = NULL; unsigned int orig_mtemp, orig_ctemp, orig_spare; const char *name = ndctl_dimm_get_devname(dimm); unsigned int alarm, mtemp, ctemp, spare, valid; int rc, i; if (!s_cmd) { fprintf(stderr, "%s: no smart command support\n", name); goto out; } rc = ndctl_cmd_submit(s_cmd); if (rc < 0) { fprintf(stderr, "%s: smart command failed: %d %s\n", name, rc, strerror(errno)); goto out; } valid = ndctl_cmd_smart_get_flags(s_cmd); alarm = ndctl_cmd_smart_get_alarm_flags(s_cmd); mtemp = ndctl_cmd_smart_get_media_temperature(s_cmd); ctemp = ndctl_cmd_smart_get_ctrl_temperature(s_cmd); spare = ndctl_cmd_smart_get_spares(s_cmd); fprintf(stderr, "%s: (smart) alarm%s: %#x mtemp%s: %.2f ctemp%s: %.2f spares%s: %d\n", name, valid & ND_SMART_ALARM_VALID ? "" : "(invalid)", alarm, valid & ND_SMART_MTEMP_VALID ? "" : "(invalid)", ndctl_decode_smart_temperature(mtemp), valid & ND_SMART_CTEMP_VALID ? "" : "(invalid)", ndctl_decode_smart_temperature(ctemp), valid & ND_SMART_SPARES_VALID ? "" : "(invalid)", spare); st_cmd = ndctl_dimm_cmd_new_smart_threshold(dimm); if (!st_cmd) { fprintf(stderr, "%s: no smart threshold command support\n", name); goto out; } rc = ndctl_cmd_submit(st_cmd); if (rc < 0) { fprintf(stderr, "%s: smart threshold command failed: %d %s\n", name, rc, strerror(errno)); goto out; } alarm = ndctl_cmd_smart_threshold_get_alarm_control(st_cmd); mtemp = ndctl_cmd_smart_threshold_get_media_temperature(st_cmd); ctemp = ndctl_cmd_smart_threshold_get_ctrl_temperature(st_cmd); spare = ndctl_cmd_smart_threshold_get_spares(st_cmd); fprintf(stderr, "%s: (smart thresh) alarm: %#x mtemp: %.2f ctemp: %.2f spares: %d\n", name, alarm, ndctl_decode_smart_temperature(mtemp), ndctl_decode_smart_temperature(ctemp), spare); orig_mtemp = mtemp; orig_ctemp = ctemp; orig_spare = spare; sst_cmd = ndctl_dimm_cmd_new_smart_set_threshold(st_cmd); if (!sst_cmd) { fprintf(stderr, "%s: no smart set threshold command support\n", name); goto out; } alarm = ndctl_cmd_smart_threshold_get_supported_alarms(sst_cmd); if (!alarm) { fprintf(stderr, "%s: no smart set threshold command support\n", name); goto out; } fprintf(stderr, "%s: supported alarms: %#x\n", name, alarm); /* * free the cmd now since we only needed the alarms and will * create + issue a set_threshold test for each alarm */ ndctl_cmd_unref(sst_cmd); for (i = 0; i < 3; i++) { unsigned int set_alarm = 1 << i; if (!(alarm & set_alarm)) continue; sst_cmd = ndctl_dimm_cmd_new_smart_set_threshold(st_cmd); if (!sst_cmd) { fprintf(stderr, "%s: alloc failed: smart set threshold\n", name); goto out; } switch (set_alarm) { case ND_SMART_SPARE_TRIP: fprintf(stderr, "%s: set spare threshold: 99\n", name); ndctl_cmd_smart_threshold_set_spares(sst_cmd, 99); ndctl_cmd_smart_threshold_set_media_temperature( sst_cmd, orig_mtemp); ndctl_cmd_smart_threshold_set_ctrl_temperature( sst_cmd, orig_ctemp); break; case ND_SMART_MTEMP_TRIP: mtemp = ndctl_cmd_smart_get_media_temperature(s_cmd); if (mtemp & (1 << 15)) mtemp *= 2; else mtemp /= 2; fprintf(stderr, "%s: set mtemp threshold: %.2f\n", name, ndctl_decode_smart_temperature(mtemp)); ndctl_cmd_smart_threshold_set_spares( sst_cmd, orig_spare); ndctl_cmd_smart_threshold_set_media_temperature( sst_cmd, mtemp); ndctl_cmd_smart_threshold_set_ctrl_temperature( sst_cmd, orig_ctemp); break; case ND_SMART_CTEMP_TRIP: ctemp = ndctl_cmd_smart_get_ctrl_temperature(s_cmd); if (ctemp & (1 << 15)) ctemp *= 2; else ctemp /= 2; fprintf(stderr, "%s: set ctemp threshold: %.2f\n", name, ndctl_decode_smart_temperature(ctemp)); ndctl_cmd_smart_threshold_set_spares( sst_cmd, orig_spare); ndctl_cmd_smart_threshold_set_media_temperature( sst_cmd, orig_mtemp); ndctl_cmd_smart_threshold_set_ctrl_temperature( sst_cmd, ctemp); break; default: break; } ndctl_cmd_smart_threshold_set_alarm_control(sst_cmd, set_alarm); rc = ndctl_cmd_submit(sst_cmd); if (rc < 0) { fprintf(stderr, "%s: smart set threshold command failed: %d %s\n", name, rc, strerror(errno)); goto out; } ndctl_cmd_unref(sst_cmd); } fprintf(stderr, "%s: set thresholds back to defaults\n", name); sst_cmd = ndctl_dimm_cmd_new_smart_set_threshold(st_cmd); if (!sst_cmd) { fprintf(stderr, "%s: alloc failed: smart set threshold\n", name); goto out; } rc = ndctl_cmd_submit(sst_cmd); if (rc < 0) { fprintf(stderr, "%s: smart set threshold defaults failed: %d %s\n", name, rc, strerror(errno)); goto out; } out: ndctl_cmd_unref(sst_cmd); ndctl_cmd_unref(st_cmd); ndctl_cmd_unref(s_cmd); } static void test_dimm_notify(struct ndctl_bus *bus) { struct ndctl_dimm *dimm; ndctl_dimm_foreach(bus, dimm) do_notify(dimm); } int main(int argc, char *argv[]) { struct ndctl_ctx *ctx; struct ndctl_bus *bus; int rc = EXIT_FAILURE; const char *provider; rc = ndctl_new(&ctx); if (rc < 0) return EXIT_FAILURE; if (argc != 2) { fprintf(stderr, "usage: smart-notify \n"); goto out; } ndctl_set_log_priority(ctx, LOG_DEBUG); provider = argv[1]; bus = ndctl_bus_get_by_provider(ctx, provider); if (!bus) { fprintf(stderr, "smart-notify: unable to find bus (%s)\n", provider); goto out; } rc = EXIT_SUCCESS; test_dimm_notify(bus); out: ndctl_unref(ctx); return rc; } ndctl-81/test/sub-section.sh000077500000000000000000000032501476737544500162070ustar00rootroot00000000000000#!/bin/bash -x # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2015-2020 Intel Corporation. All rights reserved. set -e SKIP=77 FAIL=1 SUCCESS=0 . $(dirname $0)/common check_min_kver "5.3" || do_skip "may lack align sub-section hotplug support" MNT=test_dax_mnt mkdir -p $MNT TEST_SIZE=$((16<<20)) MIN_AVAIL=$((TEST_SIZE*4)) MAX_NS=10 NAME="subsection-test" ndctl list -N | jq -r ".[] | select(.name==\"subsection-test\") | .dev" rc=$FAIL cleanup() { if [ $rc -ne $SUCCESS ]; then echo "test/sub-section.sh: failed at line $1" fi if mountpoint -q $MNT; then umount $MNT fi rm -rf $MNT # opportunistic cleanup, not fatal if these fail namespaces=$($NDCTL list -N | jq -r ".[] | select(.name==\"$NAME\") | .dev") for i in $namespaces do if ! $NDCTL destroy-namespace -f $i; then echo "test/sub-section.sh: cleanup() failed to destroy $i" fi done exit $rc } trap 'err $LINENO cleanup' ERR json=$($NDCTL list -R -b ACPI.NFIT) region=$(echo $json | jq -r "[.[] | select(.available_size >= $MIN_AVAIL)][0].dev") avail=$(echo $json | jq -r "[.[] | select(.available_size >= $MIN_AVAIL)][0].available_size") if [ -z $region ]; then exit $SKIP fi iter=$((avail/TEST_SIZE)) if [ $iter -gt $MAX_NS ]; then iter=$MAX_NS; fi for i in $(seq 1 $iter) do json=$($NDCTL create-namespace -s $TEST_SIZE --no-autorecover -r $region -n "$NAME") dev=$(echo $json | jq -r ".blockdev") mkfs.ext4 -b 4096 /dev/$dev mount -o dax /dev/$dev $MNT umount $MNT done namespaces=$($NDCTL list -N | jq -r ".[] | select(.name==\"$NAME\") | .dev") for i in $namespaces do $NDCTL disable-namespace $i $NDCTL enable-namespace $i $NDCTL destroy-namespace $i -f done rc=$SUCCESS cleanup $LINENO ndctl-81/test/track-uuid.sh000077500000000000000000000017471476737544500160350ustar00rootroot00000000000000#!/bin/bash -Ex # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2018-2020 Intel Corporation. All rights reserved. blockdev="" rc=77 . $(dirname $0)/common set -e trap 'err $LINENO' ERR # setup (reset nfit_test dimms) modprobe nfit_test reset rc=1 # create a fsdax namespace and clear errors (if any) dev="x" json=$($NDCTL create-namespace -b $NFIT_TEST_BUS0 -t pmem -m raw) eval "$(echo "$json" | json2var)" [ $dev = "x" ] && echo "fail: $LINENO" && exit 1 $NDCTL disable-namespace $dev # On broken kernels this reassignment of capacity triggers a warning # with the following signature, and results in ENXIO. # WARNING: CPU: 11 PID: 1378 at drivers/nvdimm/label.c:721 __pmem_label_update+0x55d/0x570 [libnvdimm] # Call Trace: # nd_pmem_namespace_label_update+0xd6/0x160 [libnvdimm] # uuid_store+0x15c/0x170 [libnvdimm] # kernfs_fop_write+0xf0/0x1a0 # __vfs_write+0x26/0x150 uuidgen > /sys/bus/nd/devices/$dev/uuid $NDCTL enable-namespace $dev _cleanup exit 0 ndctl-81/tools/000077500000000000000000000000001476737544500135765ustar00rootroot00000000000000ndctl-81/tools/meson-vcs-tag.sh000077500000000000000000000011701476737544500166170ustar00rootroot00000000000000#!/usr/bin/env bash # SPDX-License-Identifier: LGPL-2.1-or-later set -eu set -o pipefail dir="${1:?}" fallback="${2:?}" # Apparently git describe has a bug where it always considers the work-tree # dirty when invoked with --git-dir (even though 'git status' is happy). Work # around this issue by cd-ing to the source directory. cd "$dir" # Check that we have either .git/ (a normal clone) or a .git file (a work-tree) # and that we don't get confused if a tarball is extracted in a higher-level # git repository. [ -e .git ] && git describe --abbrev=7 --dirty=+ 2>/dev/null | \ sed -e 's/^v//' -e 's/-/./g' || echo "$fallback" ndctl-81/util/000077500000000000000000000000001476737544500134135ustar00rootroot00000000000000ndctl-81/util/abspath.c000066400000000000000000000011361476737544500152020ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 /* originally copied from git/abspath.c */ #include #include char *prefix_filename(const char *pfx, const char *arg) { struct strbuf path = STRBUF_INIT; size_t pfx_len = pfx ? strlen(pfx) : 0; if (pfx_len && !is_absolute_path(arg)) strbuf_add(&path, pfx, pfx_len); strbuf_addstr(&path, arg); return strbuf_detach(&path, NULL); } void fix_filename(const char *prefix, const char **file) { if (!file || !*file || !prefix || is_absolute_path(*file) || !strcmp("-", *file)) return; *file = prefix_filename(prefix, *file); } ndctl-81/util/bitmap.c000066400000000000000000000060741476737544500150420ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2017-2020 Intel Corporation. All rights reserved. // Copyright (C) 2009 Akinobu Mita. All rights reserved. /* originally copied from the Linux kernel bitmap implementation */ #include #include #include #include #include #include #include unsigned long *bitmap_alloc(unsigned long nbits) { return calloc(BITS_TO_LONGS(nbits), sizeof(unsigned long)); } void bitmap_set(unsigned long *map, unsigned int start, int len) { unsigned long *p = map + BIT_WORD(start); const unsigned int size = start + len; int bits_to_set = BITS_PER_LONG - (start % BITS_PER_LONG); unsigned long mask_to_set = BITMAP_FIRST_WORD_MASK(start); while (len - bits_to_set >= 0) { *p |= mask_to_set; len -= bits_to_set; bits_to_set = BITS_PER_LONG; mask_to_set = ~0UL; p++; } if (len) { mask_to_set &= BITMAP_LAST_WORD_MASK(size); *p |= mask_to_set; } } void bitmap_clear(unsigned long *map, unsigned int start, int len) { unsigned long *p = map + BIT_WORD(start); const unsigned int size = start + len; int bits_to_clear = BITS_PER_LONG - (start % BITS_PER_LONG); unsigned long mask_to_clear = BITMAP_FIRST_WORD_MASK(start); while (len - bits_to_clear >= 0) { *p &= ~mask_to_clear; len -= bits_to_clear; bits_to_clear = BITS_PER_LONG; mask_to_clear = ~0UL; p++; } if (len) { mask_to_clear &= BITMAP_LAST_WORD_MASK(size); *p &= ~mask_to_clear; } } /** * test_bit - Determine whether a bit is set * @nr: bit number to test * @addr: Address to start counting from */ int test_bit(unsigned int nr, const volatile unsigned long *addr) { return 1UL & (addr[BIT_WORD(nr)] >> (nr & (BITS_PER_LONG-1))); } /* * This is a common helper function for find_next_bit and * find_next_zero_bit. The difference is the "invert" argument, which * is XORed with each fetched word before searching it for one bits. */ static unsigned long _find_next_bit(const unsigned long *addr, unsigned long nbits, unsigned long start, unsigned long invert) { unsigned long tmp; if (!nbits || start >= nbits) return nbits; tmp = addr[start / BITS_PER_LONG] ^ invert; /* Handle 1st word. */ tmp &= BITMAP_FIRST_WORD_MASK(start); start = round_down(start, BITS_PER_LONG); while (!tmp) { start += BITS_PER_LONG; if (start >= nbits) return nbits; tmp = addr[start / BITS_PER_LONG] ^ invert; } return min(start + __builtin_ffsl(tmp), nbits); } /* * Find the next set bit in a memory region. */ unsigned long find_next_bit(const unsigned long *addr, unsigned long size, unsigned long offset) { return _find_next_bit(addr, size, offset, 0UL); } unsigned long find_next_zero_bit(const unsigned long *addr, unsigned long size, unsigned long offset) { return _find_next_bit(addr, size, offset, ~0UL); } int bitmap_full(const unsigned long *src, unsigned int nbits) { if (small_const_nbits(nbits)) return ! (~(*src) & BITMAP_LAST_WORD_MASK(nbits)); return find_next_zero_bit(src, nbits, 0UL) == nbits; } ndctl-81/util/bitmap.h000066400000000000000000000074501476737544500150460ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (C) 2015-2020 Intel Corporation. All rights reserved. */ #ifndef _NDCTL_BITMAP_H_ #define _NDCTL_BITMAP_H_ #include #include #include #include #ifndef _UL #define _UL(x) (_AC(x, UL)) #endif #ifndef _ULL #define _ULL(x) (_AC(x, ULL)) #endif #define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) #define UL(x) (_UL(x)) #define ULL(x) (_ULL(x)) /* GENMASK() and its dependencies copied from include/linux/{bits.h, const.h} */ #define __is_constexpr(x) \ (sizeof(int) == sizeof(*(8 ? ((void *)((long)(x) * 0l)) : (int *)8))) #define GENMASK_INPUT_CHECK(h, l) \ (BUILD_BUG_ON_ZERO(__builtin_choose_expr( \ __is_constexpr((l) > (h)), (l) > (h), 0))) #define __GENMASK(h, l) \ (((~UL(0)) - (UL(1) << (l)) + 1) & \ (~UL(0) >> (BITS_PER_LONG - 1 - (h)))) #define GENMASK(h, l) \ (GENMASK_INPUT_CHECK(h, l) + __GENMASK(h, l)) #define BIT(nr) (1UL << (nr)) #define BIT_MASK(nr) (1UL << ((nr) % BITS_PER_LONG)) #define BIT_WORD(nr) ((nr) / BITS_PER_LONG) #define BITS_PER_BYTE 8 #define BITS_TO_LONGS(nr) DIV_ROUND_UP(nr, BITS_PER_BYTE * sizeof(long)) #define BITMAP_FIRST_WORD_MASK(start) (~0UL << ((start) & (BITS_PER_LONG - 1))) #define BITMAP_LAST_WORD_MASK(nbits) (~0UL >> (-(nbits) & (BITS_PER_LONG - 1))) #define small_const_nbits(nbits) \ (__builtin_constant_p(nbits) && (nbits) <= BITS_PER_LONG) unsigned long *bitmap_alloc(unsigned long nbits); void bitmap_set(unsigned long *map, unsigned int start, int len); void bitmap_clear(unsigned long *map, unsigned int start, int len); int test_bit(unsigned int nr, const volatile unsigned long *addr); unsigned long find_next_bit(const unsigned long *addr, unsigned long size, unsigned long offset); unsigned long find_next_zero_bit(const unsigned long *addr, unsigned long size, unsigned long offset); int bitmap_full(const unsigned long *src, unsigned int nbits); /* * Bitfield access macros * (Copied from Linux's include/linux/bitfield.h) * * FIELD_{GET,PREP} macros take as first parameter shifted mask * from which they extract the base mask and shift amount. * Mask must be a compilation time constant. * * Example: * * #define REG_FIELD_A GENMASK(6, 0) * #define REG_FIELD_B BIT(7) * #define REG_FIELD_C GENMASK(15, 8) * #define REG_FIELD_D GENMASK(31, 16) * * Get: * a = FIELD_GET(REG_FIELD_A, reg); * b = FIELD_GET(REG_FIELD_B, reg); * * Set: * reg = FIELD_PREP(REG_FIELD_A, 1) | * FIELD_PREP(REG_FIELD_B, 0) | * FIELD_PREP(REG_FIELD_C, c) | * FIELD_PREP(REG_FIELD_D, 0x40); * * Modify: * reg &= ~REG_FIELD_C; * reg |= FIELD_PREP(REG_FIELD_C, c); */ /* Force a compilation error if a constant expression is not a power of 2 */ #define __BUILD_BUG_ON_NOT_POWER_OF_2(n) \ BUILD_BUG_ON(((n) & ((n) - 1)) != 0) #define BUILD_BUG_ON_NOT_POWER_OF_2(n) \ BUILD_BUG_ON((n) == 0 || (((n) & ((n) - 1)) != 0)) #define __bf_shf(x) (__builtin_ffsll(x) - 1) #define __BF_FIELD_CHECK(_mask, _reg, _val) \ ({ \ BUILD_BUG_ON(!__builtin_constant_p(_mask)); \ BUILD_BUG_ON((_mask) == 0); \ BUILD_BUG_ON(__builtin_constant_p(_val) ? \ ~((_mask) >> __bf_shf(_mask)) & (_val) : 0); \ BUILD_BUG_ON((_mask) > (typeof(_reg))~0ull); \ __BUILD_BUG_ON_NOT_POWER_OF_2((_mask) + \ (1ULL << __bf_shf(_mask))); \ }) /** * FIELD_GET() - extract a bitfield element * @_mask: shifted mask defining the field's length and position * @_reg: value of entire bitfield * * FIELD_GET() extracts the field specified by @_mask from the * bitfield passed in as @_reg by masking and shifting it down. */ #define FIELD_GET(_mask, _reg) \ ({ \ __BF_FIELD_CHECK(_mask, _reg, 0U); \ (typeof(_mask))(((_reg) & (_mask)) >> __bf_shf(_mask)); \ }) #endif /* _NDCTL_BITMAP_H_ */ ndctl-81/util/event_trace.c000066400000000000000000000141321476737544500160570ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2022, Intel Corp. All rights reserved. #include #include #include #include #include #include #include #include #include #include #include "event_trace.h" #define _GNU_SOURCE #include u64 trace_get_field_u64(struct tep_event *event, struct tep_record *record, const char *name) { unsigned long long val; if (tep_get_field_val(NULL, event, name, record, &val, 0)) return ULLONG_MAX; return val; } u32 trace_get_field_u32(struct tep_event *event, struct tep_record *record, const char *name) { char *val; int len; val = tep_get_field_raw(NULL, event, name, record, &len, 0); if (!val) return UINT_MAX; return *(u32 *)val; } u8 trace_get_field_u8(struct tep_event *event, struct tep_record *record, const char *name) { char *val; int len; val = tep_get_field_raw(NULL, event, name, record, &len, 0); if (!val) return UCHAR_MAX; return *(u8 *)val; } static struct json_object *num_to_json(void *num, int elem_size, unsigned long flags) { bool sign = flags & TEP_FIELD_IS_SIGNED; int64_t val = 0; /* special case 64 bit as the call depends on sign */ if (elem_size == 8) { if (sign) return json_object_new_int64(*(int64_t *)num); else return util_json_new_u64(*(uint64_t *)num); } /* All others fit in a signed 64 bit */ switch (elem_size) { case 1: if (sign) val = *(int8_t *)num; else val = *(uint8_t *)num; break; case 2: if (sign) val = *(int16_t *)num; else val = *(uint16_t *)num; break; case 4: if (sign) val = *(int32_t *)num; else val = *(uint32_t *)num; break; default: /* * Odd sizes are converted in the kernel to one of the above. * It is an error to see them here. */ return NULL; } return json_object_new_int64(val); } static int event_to_json(struct tep_event *event, struct tep_record *record, struct event_ctx *ctx) { struct json_object *jevent, *jobj, *jarray; struct tep_format_field **fields; struct jlist_node *jnode; int i, j, rc = 0; jnode = malloc(sizeof(*jnode)); if (!jnode) return -ENOMEM; jevent = json_object_new_object(); if (!jevent) { rc = -ENOMEM; goto err_jnode; } jnode->jobj = jevent; fields = tep_event_fields(event); if (!fields) { rc = -ENOENT; goto err_jevent; } jobj = json_object_new_string(event->system); if (!jobj) { rc = -ENOMEM; goto err_jevent; } json_object_object_add(jevent, "system", jobj); jobj = json_object_new_string(event->name); if (!jobj) { rc = -ENOMEM; goto err_jevent; } json_object_object_add(jevent, "event", jobj); jobj = util_json_new_u64(record->ts); if (!jobj) { rc = -ENOMEM; goto err_jevent; } json_object_object_add(jevent, "timestamp", jobj); for (i = 0; fields[i]; i++) { struct tep_format_field *f = fields[i]; int len; /* * libtraceevent differentiates arrays and strings like this: * array: TEP_FIELD_IS_[ARRAY | STRING] * string: TEP_FIELD_IS_[ARRAY | STRING | DYNAMIC] */ if ((f->flags & TEP_FIELD_IS_STRING) && ((f->flags & TEP_FIELD_IS_DYNAMIC))) { char *str; str = tep_get_field_raw(NULL, event, f->name, record, &len, 0); if (!str) continue; jobj = json_object_new_string(str); if (!jobj) { rc = -ENOMEM; goto err_jevent; } json_object_object_add(jevent, f->name, jobj); } else if (f->flags & TEP_FIELD_IS_ARRAY) { unsigned char *data; int chunks; data = tep_get_field_raw(NULL, event, f->name, record, &len, 0); if (!data) continue; jarray = json_object_new_array(); if (!jarray) { rc = -ENOMEM; goto err_jevent; } chunks = f->size / f->elementsize; for (j = 0; j < chunks; j++) { jobj = num_to_json(data, f->elementsize, f->flags); if (!jobj) { json_object_put(jarray); rc = -ENOMEM; goto err_jevent; } json_object_array_add(jarray, jobj); data += f->elementsize; } json_object_object_add(jevent, f->name, jarray); } else { /* single number */ unsigned char *data; char *tmp; data = tep_get_field_raw(NULL, event, f->name, record, &len, 0); if (!data) continue; /* check to see if we have a UUID */ tmp = strcasestr(f->type, "uuid_t"); if (tmp) { char uuid[40]; uuid_unparse(data, uuid); jobj = json_object_new_string(uuid); if (!jobj) { rc = -ENOMEM; goto err_jevent; } json_object_object_add(jevent, f->name, jobj); continue; } jobj = num_to_json(data, f->elementsize, f->flags); if (!jobj) { rc = -ENOMEM; goto err_jevent; } json_object_object_add(jevent, f->name, jobj); } } list_add_tail(&ctx->jlist_head, &jnode->list); return 0; err_jevent: json_object_put(jevent); err_jnode: free(jnode); return rc; } static int event_parse(struct tep_event *event, struct tep_record *record, int cpu, void *ctx) { struct event_ctx *event_ctx = (struct event_ctx *)ctx; /* Filter out all the events that the caller isn't interested in. */ if (strcmp(event->system, event_ctx->system) != 0) return 0; if (event_ctx->event_name) { if (strcmp(event->name, event_ctx->event_name) != 0) return 0; } if (event_ctx->event_pid) { if (event_ctx->event_pid != tep_data_pid(event->tep, record)) return 0; } if (event_ctx->parse_event) return event_ctx->parse_event(event, record, event_ctx); return event_to_json(event, record, event_ctx); } int trace_event_parse(struct tracefs_instance *inst, struct event_ctx *ectx) { struct tep_handle *tep; int rc; tep = tracefs_local_events(NULL); if (!tep) return -ENOMEM; rc = tracefs_iterate_raw_events(tep, inst, NULL, 0, event_parse, ectx); tep_free(tep); return rc; } int trace_event_enable(struct tracefs_instance *inst, const char *system, const char *event) { int rc; rc = tracefs_event_enable(inst, system, event); if (rc == -1) return -errno; if (tracefs_trace_is_on(inst)) return 0; tracefs_trace_on(inst); return 0; } int trace_event_disable(struct tracefs_instance *inst) { return tracefs_trace_off(inst); } ndctl-81/util/event_trace.h000066400000000000000000000024461476737544500160710ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (C) 2022 Intel Corporation. All rights reserved. */ #ifndef __UTIL_EVENT_TRACE_H__ #define __UTIL_EVENT_TRACE_H__ #include #include #include struct jlist_node { struct json_object *jobj; struct list_node list; }; struct cxl_poison_ctx { struct json_object *jpoison; struct cxl_region *region; struct cxl_memdev *memdev; }; struct event_ctx { const char *system; struct list_head jlist_head; const char *event_name; /* optional */ int event_pid; /* optional */ struct cxl_poison_ctx *poison_ctx; /* optional */ unsigned long json_flags; int (*parse_event)(struct tep_event *event, struct tep_record *record, struct event_ctx *ctx); }; int trace_event_parse(struct tracefs_instance *inst, struct event_ctx *ectx); int trace_event_enable(struct tracefs_instance *inst, const char *system, const char *event); int trace_event_disable(struct tracefs_instance *inst); u8 trace_get_field_u8(struct tep_event *event, struct tep_record *record, const char *name); u32 trace_get_field_u32(struct tep_event *event, struct tep_record *record, const char *name); u64 trace_get_field_u64(struct tep_event *event, struct tep_record *record, const char *name); #endif ndctl-81/util/fletcher.h000066400000000000000000000010331476737544500153550ustar00rootroot00000000000000#ifndef _NDCTL_FLETCHER_H_ #define _NDCTL_FLETCHER_H_ #include #include #include /* * Note, fletcher64() is copied from drivers/nvdimm/label.c in the Linux kernel */ static inline u64 fletcher64(void *addr, size_t len, bool le) { u32 *buf = addr; u32 lo32 = 0; u64 hi32 = 0; size_t i; for (i = 0; i < len / sizeof(u32); i++) { lo32 += le ? le32_to_cpu((le32) buf[i]) : buf[i]; hi32 += lo32; } return hi32 << 32 | lo32; } #endif /* _NDCTL_FLETCHER_H_ */ ndctl-81/util/help.c000066400000000000000000000070101476737544500145050ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2015-2020 Intel Corporation. All rights reserved. // Copyright (C) 2008 Miklos Vajna. All rights reserved. // Copyright (C) 2006 Linus Torvalds. All rights reserved. /* originally copied from perf and git */ /* * builtin-help.c * * Builtin help command */ #include #include #include #include #include #include #include #define pr_err(x, ...) fprintf(stderr, x, ##__VA_ARGS__) #define STRERR_BUFSIZE 128 /* For the buffer size of strerror_r */ static void exec_man_konqueror(const char *path, const char *page) { const char *display = getenv("DISPLAY"); if (display && *display) { struct strbuf man_page = STRBUF_INIT; const char *filename = "kfmclient"; char sbuf[STRERR_BUFSIZE]; /* It's simpler to launch konqueror using kfmclient. */ if (path) { const char *file = strrchr(path, '/'); if (file && !strcmp(file + 1, "konqueror")) { char *dest; char *new = strdup(path); if (!new) { pr_err("strdup(path) failed.\n"); exit(1); } dest = strrchr(new, '/'); /* strlen("konqueror") == strlen("kfmclient") */ strcpy(dest + 1, "kfmclient"); path = new; } if (file) filename = file; } else path = "kfmclient"; strbuf_addf(&man_page, "man:%s(1)", page); execlp(path, filename, "newTab", man_page.buf, NULL); warning("failed to exec '%s': %s", path, strerror_r(errno, sbuf, sizeof(sbuf))); } } static void exec_man_man(const char *path, const char *page) { char sbuf[STRERR_BUFSIZE]; if (!path) path = "man"; execlp(path, "man", page, NULL); warning("failed to exec '%s': %s", path, strerror_r(errno, sbuf, sizeof(sbuf))); } static char *cmd_to_page(const char *cmd, char **page, const char *util_name) { int rc; if (!cmd) rc = asprintf(page, "%s", util_name); else if (!prefixcmp(cmd, util_name)) rc = asprintf(page, "%s", cmd); else rc = asprintf(page, "%s-%s", util_name, cmd); if (rc < 0) return NULL; return *page; } static const char *system_path(const char *path) { static const char *prefix = PREFIX; struct strbuf d = STRBUF_INIT; if (is_absolute_path(path)) return path; strbuf_addf(&d, "%s/%s", prefix, path); path = strbuf_detach(&d, NULL); return path; } static void setup_man_path(void) { struct strbuf new_path = STRBUF_INIT; const char *old_path = getenv("MANPATH"); /* We should always put ':' after our path. If there is no * old_path, the ':' at the end will let 'man' to try * system-wide paths after ours to find the manual page. If * there is old_path, we need ':' as delimiter. */ strbuf_addstr(&new_path, system_path(NDCTL_MAN_PATH)); strbuf_addch(&new_path, ':'); if (old_path) strbuf_addstr(&new_path, old_path); setenv("MANPATH", new_path.buf, 1); strbuf_release(&new_path); } static void exec_viewer(const char *name, const char *page) { if (!strcasecmp(name, "man")) exec_man_man(NULL, page); else if (!strcasecmp(name, "konqueror")) exec_man_konqueror(NULL, page); else warning("'%s': unknown man viewer.", name); } int help_show_man_page(const char *cmd, const char *util_name, const char *viewer) { const char *fallback = getenv(viewer); char *page; page = cmd_to_page(cmd, &page, util_name); if (!page) return -1; setup_man_path(); if (fallback) exec_viewer(fallback, page); exec_viewer("man", page); pr_err("no man viewer handled the request"); free(page); return -1; } ndctl-81/util/iomem.c000066400000000000000000000015331476737544500146670ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2019-2020 Intel Corporation. All rights reserved. #include #include #include #include #include #include unsigned long long __iomem_get_dev_resource(struct log_ctx *ctx, const char *devpath) { const char *devname = devpath_to_devname(devpath); FILE *fp = fopen("/proc/iomem", "r"); unsigned long long res; char name[256]; if (fp == NULL) { log_err(ctx, "%s: open /proc/iomem: %s\n", devname, strerror(errno)); return 0; } while (fscanf(fp, "%llx-%*x : %254[^\n]\n", &res, name) == 2) { if (strcmp(name, devname) == 0) { log_dbg(ctx, "%s: got resource via iomem: %#llx\n", devname, res); fclose(fp); return res; } } log_dbg(ctx, "%s: not found in iomem\n", devname); fclose(fp); return 0; } ndctl-81/util/iomem.h000066400000000000000000000005611476737544500146740ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (C) 2019-2020 Intel Corporation. All rights reserved. */ #ifndef _NDCTL_IOMEM_H_ #define _NDCTL_IOMEM_H_ struct log_ctx; unsigned long long __iomem_get_dev_resource(struct log_ctx *ctx, const char *path); #define iomem_get_dev_resource(c, p) __iomem_get_dev_resource(&(c)->ctx, (p)) #endif /* _NDCTL_IOMEM_H_ */ ndctl-81/util/json.c000066400000000000000000000066071476737544500145410ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2015-2020 Intel Corporation. All rights reserved. #include #include #include #include #include #include #include #include /* adapted from mdadm::human_size_brief() */ static int display_size(struct json_object *jobj, struct printbuf *pbuf, int level, int flags) { unsigned long long bytes = json_object_get_int64(jobj); static char buf[128]; int c; /* * We convert bytes to either centi-M{ega,ibi}bytes or * centi-G{igi,ibi}bytes, with appropriate rounding, and then print * 1/100th of those as a decimal. We allow upto 2048Megabytes before * converting to gigabytes, as that shows more precision and isn't too * large a number. Terabytes are not yet handled. * * If prefix == IEC, we mean prefixes like kibi,mebi,gibi etc. * If prefix == JEDEC, we mean prefixes like kilo,mega,giga etc. */ if (bytes < 5000*SZ_1K) snprintf(buf, sizeof(buf), "%lld", bytes); else { /* IEC */ if (bytes < 2L*SZ_1G) { long cMiB = (bytes * 200LL / SZ_1M+1) /2; c = snprintf(buf, sizeof(buf), "\"%ld.%02ld MiB", cMiB/100 , cMiB % 100); } else if (bytes < 2*SZ_1T) { long cGiB = (bytes * 200LL / SZ_1G+1) /2; c = snprintf(buf, sizeof(buf), "\"%ld.%02ld GiB", cGiB/100 , cGiB % 100); } else { long cTiB = (bytes * 200LL / SZ_1T+1) /2; c = snprintf(buf, sizeof(buf), "\"%ld.%02ld TiB", cTiB/100 , cTiB % 100); } /* JEDEC */ if (bytes < 2L*SZ_1G) { long cMB = (bytes / (1000000LL / 200LL) + 1) / 2; snprintf(buf + c, sizeof(buf) - c, " (%ld.%02ld MB)\"", cMB/100, cMB % 100); } else if (bytes < 2*SZ_1T) { long cGB = (bytes / (1000000000LL/200LL) + 1) / 2; snprintf(buf + c, sizeof(buf) - c, " (%ld.%02ld GB)\"", cGB/100 , cGB % 100); } else { long cTB = (bytes / (1000000000000LL/200LL) + 1) / 2; snprintf(buf + c, sizeof(buf) - c, " (%ld.%02ld TB)\"", cTB/100 , cTB % 100); } } return printbuf_memappend(pbuf, buf, strlen(buf)); } static int display_hex(struct json_object *jobj, struct printbuf *pbuf, int level, int flags) { unsigned long long val = util_json_get_u64(jobj); static char buf[32]; snprintf(buf, sizeof(buf), "\"%#llx\"", val); return printbuf_memappend(pbuf, buf, strlen(buf)); } struct json_object *util_json_object_size(unsigned long long size, unsigned long flags) { struct json_object *jobj = json_object_new_int64(size); if (jobj && (flags & UTIL_JSON_HUMAN)) json_object_set_serializer(jobj, display_size, NULL, NULL); return jobj; } struct json_object *util_json_object_hex(unsigned long long val, unsigned long flags) { struct json_object *jobj = util_json_new_u64(val); if (jobj && (flags & UTIL_JSON_HUMAN)) json_object_set_serializer(jobj, display_hex, NULL, NULL); return jobj; } void util_display_json_array(FILE *f_out, struct json_object *jarray, unsigned long flags) { int len = json_object_array_length(jarray); int jflag = JSON_C_TO_STRING_PRETTY; if (len > 1 || !(flags & UTIL_JSON_HUMAN)) { if (len == 0) warning("no matching devices found\n"); fprintf(f_out, "%s\n", json_object_to_json_string_ext(jarray, jflag)); } else if (len) { struct json_object *jobj; jobj = json_object_array_get_idx(jarray, 0); fprintf(f_out, "%s\n", json_object_to_json_string_ext(jobj, jflag)); } json_object_put(jarray); } ndctl-81/util/json.h000066400000000000000000000030311476737544500145320ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (C) 2015-2020 Intel Corporation. All rights reserved. */ #ifndef __UTIL_JSON_H__ #define __UTIL_JSON_H__ #include #include #include enum util_json_flags { UTIL_JSON_IDLE = (1 << 0), UTIL_JSON_MEDIA_ERRORS = (1 << 1), UTIL_JSON_DAX = (1 << 2), UTIL_JSON_DAX_DEVS = (1 << 3), UTIL_JSON_HUMAN = (1 << 4), UTIL_JSON_VERBOSE = (1 << 5), UTIL_JSON_CAPABILITIES = (1 << 6), UTIL_JSON_CONFIGURED = (1 << 7), UTIL_JSON_FIRMWARE = (1 << 8), UTIL_JSON_DAX_MAPPINGS = (1 << 9), UTIL_JSON_HEALTH = (1 << 10), UTIL_JSON_TARGETS = (1 << 11), UTIL_JSON_PARTITION = (1 << 12), UTIL_JSON_ALERT_CONFIG = (1 << 13), }; void util_display_json_array(FILE *f_out, struct json_object *jarray, unsigned long flags); struct json_object *util_json_object_size(unsigned long long size, unsigned long flags); struct json_object *util_json_object_hex(unsigned long long val, unsigned long flags); #if HAVE_JSON_U64 static inline struct json_object *util_json_new_u64(unsigned long long val) { return json_object_new_uint64(val); } static inline unsigned long long util_json_get_u64(struct json_object *jobj) { return json_object_get_uint64(jobj); } #else /* fallback to signed */ static inline struct json_object *util_json_new_u64(unsigned long long val) { return json_object_new_int64(val); } static inline unsigned long long util_json_get_u64(struct json_object *jobj) { return json_object_get_int64(jobj); } #endif /* HAVE_JSON_U64 */ #endif /* __UTIL_JSON_H__ */ ndctl-81/util/list.h000066400000000000000000000037061476737544500145450ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (C) 2022 Intel Corporation. All rights reserved. */ #ifndef _NDCTL_LIST_H_ #define _NDCTL_LIST_H_ #include #define list_add_sorted(head, n, node, cmp) \ do { \ struct list_head *__head = (head); \ typeof(n) __iter, __next; \ typeof(n) __new = (n); \ \ if (list_empty(__head)) { \ list_add(__head, &__new->node); \ break; \ } \ \ list_for_each (__head, __iter, node) { \ if (cmp(__new, __iter) < 0) { \ list_add_before(__head, &__iter->node, \ &__new->node); \ break; \ } \ __next = list_next(__head, __iter, node); \ if (!__next) { \ list_add_after(__head, &__iter->node, \ &__new->node); \ break; \ } \ if (cmp(__new, __next) < 0) { \ list_add_before(__head, &__next->node, \ &__new->node); \ break; \ } \ } \ } while (0) #endif /* _NDCTL_LIST_H_ */ ndctl-81/util/log.c000066400000000000000000000043521476737544500143440ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-2.1 // Copyright (C) 2016-2020, Intel Corporation. All rights reserved. #include #include #include #include #include #include #include #include void log_syslog(struct log_ctx *ctx, int priority, const char *file, int line, const char *fn, const char *format, va_list args) { vsyslog(priority, format, args); } void log_standard(struct log_ctx *ctx, int priority, const char *file, int line, const char *fn, const char *format, va_list args) { if (priority == 6) vfprintf(stdout, format, args); else vfprintf(stderr, format, args); } void log_file(struct log_ctx *ctx, int priority, const char *file, int line, const char *fn, const char *format, va_list args) { FILE *f = ctx->log_file; if (priority != LOG_NOTICE) { struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); fprintf(f, "[%10ld.%09ld] [%d] ", ts.tv_sec, ts.tv_nsec, getpid()); } vfprintf(f, format, args); fflush(f); } void do_log(struct log_ctx *ctx, int priority, const char *file, int line, const char *fn, const char *format, ...) { va_list args; int errno_save = errno; va_start(args, format); ctx->log_fn(ctx, priority, file, line, fn, format, args); va_end(args); errno = errno_save; } static void log_stderr(struct log_ctx *ctx, int priority, const char *file, int line, const char *fn, const char *format, va_list args) { fprintf(stderr, "%s: %s: ", ctx->owner, fn); vfprintf(stderr, format, args); } static int log_priority(const char *priority) { char *endptr; int prio; prio = strtol(priority, &endptr, 10); if (endptr[0] == '\0' || isspace(endptr[0])) return prio; if (strncmp(priority, "err", 3) == 0) return LOG_ERR; if (strncmp(priority, "info", 4) == 0) return LOG_INFO; if (strncmp(priority, "debug", 5) == 0) return LOG_DEBUG; if (strncmp(priority, "notice", 6) == 0) return LOG_NOTICE; return 0; } void log_init(struct log_ctx *ctx, const char *owner, const char *log_env) { const char *env; ctx->owner = owner; ctx->log_fn = log_stderr; ctx->log_priority = LOG_ERR; /* environment overwrites config */ env = secure_getenv(log_env); if (env != NULL) ctx->log_priority = log_priority(env); } ndctl-81/util/log.h000066400000000000000000000046371476737544500143570ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1 */ /* Copyright (C) 2016-2020, Intel Corporation. All rights reserved. */ #ifndef __UTIL_LOG_H__ #define __UTIL_LOG_H__ #include #include #include struct log_ctx; typedef void (*log_fn)(struct log_ctx *ctx, int priority, const char *file, int line, const char *fn, const char *format, va_list args); struct log_ctx { log_fn log_fn; const char *owner; int log_priority; FILE *log_file; }; void log_syslog(struct log_ctx *ctx, int priority, const char *file, int line, const char *fn, const char *format, va_list args); void log_standard(struct log_ctx *ctx, int priority, const char *file, int line, const char *fn, const char *format, va_list args); void log_file(struct log_ctx *ctx, int priority, const char *file, int line, const char *fn, const char *format, va_list args); void do_log(struct log_ctx *ctx, int priority, const char *file, int line, const char *fn, const char *format, ...) __attribute__((format(printf, 6, 7))); void log_init(struct log_ctx *ctx, const char *owner, const char *log_env); static inline void __attribute__((always_inline, format(printf, 2, 3))) log_null(struct log_ctx *ctx, const char *format, ...) {} #define log_cond(ctx, prio, arg...) \ do { \ if ((ctx)->log_priority >= prio) \ do_log(ctx, prio, __FILE__, __LINE__, __FUNCTION__, ## arg); \ } while (0) #ifdef ENABLE_LOGGING # ifdef ENABLE_DEBUG # define log_dbg(ctx, arg...) log_cond(ctx, LOG_DEBUG, ## arg) # else # define log_dbg(ctx, arg...) log_null(ctx, ## arg) # endif # define log_info(ctx, arg...) log_cond(ctx, LOG_INFO, ## arg) # define log_err(ctx, arg...) log_cond(ctx, LOG_ERR, ## arg) # define log_notice(ctx, arg...) log_cond(ctx, LOG_NOTICE, ## arg) #else # define log_dbg(ctx, arg...) log_null(ctx, ## arg) # define log_info(ctx, arg...) log_null(ctx, ## arg) # define log_err(ctx, arg...) log_null(ctx, ## arg) # define log_notice(ctx, arg...) log_null(ctx, ## arg) #endif #define dbg(x, arg...) log_dbg(&(x)->ctx, ## arg) #define info(x, arg...) log_info(&(x)->ctx, ## arg) #define err(x, arg...) log_err(&(x)->ctx, ## arg) #define notice(x, arg...) log_notice(&(x)->ctx, ## arg) #ifndef HAVE_SECURE_GETENV # ifdef HAVE___SECURE_GETENV # define secure_getenv __secure_getenv # else # warning neither secure_getenv nor __secure_getenv is available. # define secure_getenv getenv # endif #endif #endif /* __UTIL_LOG_H__ */ ndctl-81/util/main.c000066400000000000000000000054501476737544500145070ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2015-2020 Intel Corporation. All rights reserved. // Copyright (C) 2006 Linus Torvalds. All rights reserved. /* originally copied from perf and git */ #include #include #include #include #include #include #include #include #include #include int main_handle_options(const char ***argv, int *argc, const char *usage_msg, struct cmd_struct *cmds, int num_cmds) { int handled = 0; while (*argc > 0) { const char *cmd = (*argv)[0]; if (cmd[0] != '-') break; if (!strcmp(cmd, "--version") || !strcmp(cmd, "--help")) break; /* * Shortcut for '-h' and '-v' options to invoke help * and version command. */ if (!strcmp(cmd, "-h")) { (*argv)[0] = "--help"; break; } if (!strcmp(cmd, "-v")) { (*argv)[0] = "--version"; break; } if (!strcmp(cmd, "--list-cmds")) { int i; for (i = 0; i < num_cmds; i++) { struct cmd_struct *p = cmds+i; /* filter out commands from auto-complete */ if (strcmp(p->cmd, "create-nfit") == 0) continue; if (strcmp(p->cmd, "test") == 0) continue; if (strcmp(p->cmd, "bat") == 0) continue; printf("%s\n", p->cmd); } exit(0); } else { fprintf(stderr, "Unknown option: %s\n", cmd); usage(usage_msg); } (*argv)++; (*argc)--; handled++; } return handled; } static int run_builtin(struct cmd_struct *p, int argc, const char **argv, void *ctx, enum program prog) { int status; struct stat st; if (prog == PROG_NDCTL) status = p->n_fn(argc, argv, ctx); else status = p->d_fn(argc, argv, ctx); if (status) return status & 0xff; /* Somebody closed stdout? */ if (fstat(fileno(stdout), &st)) return 0; /* Ignore write errors for pipes and sockets.. */ if (S_ISFIFO(st.st_mode) || S_ISSOCK(st.st_mode)) return 0; status = 1; /* Check for ENOSPC and EIO errors.. */ if (fflush(stdout)) { fprintf(stderr, "write failure on standard output: %s", strerror(errno)); goto out; } if (ferror(stdout)) { fprintf(stderr, "unknown write failure on standard output"); goto out; } if (fclose(stdout)) { fprintf(stderr, "close failed on standard output: %s", strerror(errno)); goto out; } status = 0; out: return status; } void main_handle_internal_command(int argc, const char **argv, void *ctx, struct cmd_struct *cmds, int num_cmds, enum program prog) { const char *cmd = argv[0]; int i; /* Turn " cmd --help" into " help cmd" */ if (argc > 1 && !strcmp(argv[1], "--help")) { argv[1] = argv[0]; argv[0] = cmd = "help"; } for (i = 0; i < num_cmds; i++) { struct cmd_struct *p = cmds+i; if (strcmp(p->cmd, cmd)) continue; exit(run_builtin(p, argc, argv, ctx, prog)); } } ndctl-81/util/main.h000066400000000000000000000016761476737544500145220ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (C) 2015-2020 Intel Corporation. All rights reserved. */ /* Copyright (C) 2006 Linus Torvalds. All rights reserved. */ /* originally copied from perf and git */ #ifndef __MAIN_H__ #define __MAIN_H__ enum program { PROG_NDCTL, PROG_DAXCTL, PROG_CXL, }; struct ndctl_ctx; struct daxctl_ctx; struct cxl_ctx; struct cmd_struct { const char *cmd; union { int (*n_fn)(int, const char **, struct ndctl_ctx *ctx); int (*d_fn)(int, const char **, struct daxctl_ctx *ctx); int (*c_fn)(int, const char **, struct cxl_ctx *ctx); }; }; int main_handle_options(const char ***argv, int *argc, const char *usage_msg, struct cmd_struct *cmds, int num_cmds); void main_handle_internal_command(int argc, const char **argv, void *ctx, struct cmd_struct *cmds, int num_cmds, enum program prog); int help_show_man_page(const char *cmd, const char *util_name, const char *viewer); #endif /* __MAIN_H__ */ ndctl-81/util/meson.build000066400000000000000000000005151476737544500155560ustar00rootroot00000000000000util = static_library('util', [ 'parse-options.c', 'parse-configs.c', 'usage.c', 'size.c', 'json.c', 'log.c', 'main.c', 'help.c', 'strbuf.c', 'wrapper.c', 'bitmap.c', 'abspath.c', 'iomem.c', ], dependencies: iniparser, include_directories : root_inc, ) util_dep = declare_dependency(link_with : util) ndctl-81/util/parse-configs.c000066400000000000000000000065671476737544500163350ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2021, FUJITSU LIMITED. ALL rights reserved. #include #include #include #include #include #include #include int filter_conf_files(const struct dirent *dir) { if (!dir) return 0; if (dir->d_type == DT_REG) { const char *ext = strrchr(dir->d_name, '.'); if ((!ext) || (ext == dir->d_name)) return 0; if (strcmp(ext, ".conf") == 0) return 1; } return 0; } static void set_str_val(const char **value, const char *val) { struct strbuf buf = STRBUF_INIT; size_t len = *value ? strlen(*value) : 0; if (!val) return; if (len) { strbuf_add(&buf, *value, len); strbuf_addstr(&buf, " "); } strbuf_addstr(&buf, val); *value = strbuf_detach(&buf, NULL); } static const char *search_section_kv(dictionary *d, const struct config *c) { int i; for (i = 0; i < iniparser_getnsec(d); i++) { const char *cur_sec_full = iniparser_getsecname(d, i); char *cur_sec = strdup(cur_sec_full); const char *search_val, *ret_val; const char *delim = " \t\n\r"; char *save, *cur, *query; if (!cur_sec) return NULL; if (!c->section || !c->search_key || !c->search_val || !c->get_key) { fprintf(stderr, "warning: malformed config query, skipping\n"); goto out_sec; } cur = strtok_r(cur_sec, delim, &save); if ((cur == NULL) || (strcmp(cur, c->section) != 0)) goto out_sec; if (asprintf(&query, "%s:%s", cur_sec_full, c->search_key) < 0) goto out_sec; search_val = iniparser_getstring(d, query, NULL); if (!search_val) goto out_query; if (strcmp(search_val, c->search_val) != 0) goto out_query; /* we're now in a matching section */ free(query); if (asprintf(&query, "%s:%s", cur_sec_full, c->get_key) < 0) goto out_sec; ret_val = iniparser_getstring(d, query, NULL); free(query); free(cur_sec); return ret_val; out_query: free(query); out_sec: free(cur_sec); } return NULL; } static int parse_config_file(const char *config_file, const struct config *configs) { dictionary *dic; if ((configs->type == MONITOR_CALLBACK) && (strcmp(config_file, configs->key) == 0)) return configs->callback(configs, configs->key); dic = iniparser_load(config_file); if (!dic) return -errno; for (; configs->type != CONFIG_END; configs++) { switch (configs->type) { case CONFIG_STRING: set_str_val((const char **)configs->value, iniparser_getstring(dic, configs->key, configs->defval)); break; case CONFIG_SEARCH_SECTION: set_str_val((const char **)configs->value, search_section_kv(dic, configs)); case MONITOR_CALLBACK: case CONFIG_END: break; } } iniparser_freedict(dic); return 0; } int parse_configs_prefix(const char *config_path, const char *prefix, const struct config *configs) { const char *config_file = NULL; struct dirent **namelist; int rc, count; if (configs->type == MONITOR_CALLBACK) return parse_config_file(config_path, configs); count = scandir(config_path, &namelist, filter_conf_files, alphasort); if (count == -1) return -errno; while (count--) { char *conf_abspath; config_file = namelist[count]->d_name; rc = asprintf(&conf_abspath, "%s/%s", config_path, config_file); if (rc < 0) return -ENOMEM; rc = parse_config_file(conf_abspath, configs); free(conf_abspath); if (rc) return rc; } return 0; } ndctl-81/util/parse-configs.h000066400000000000000000000025221476737544500163250ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2021, FUJITSU LIMITED. ALL rights reserved. #include #include #include #include #include enum parse_conf_type { CONFIG_STRING, CONFIG_SEARCH_SECTION, CONFIG_END, MONITOR_CALLBACK, }; int filter_conf_files(const struct dirent *dir); struct config; typedef int parse_conf_cb(const struct config *, const char *config_file); struct config { enum parse_conf_type type; const char *section; const char *search_key; const char *search_val; const char *get_key; const char *key; void *value; void *defval; parse_conf_cb *callback; }; #define check_vtype(v, type) ( BUILD_BUG_ON_ZERO(!__builtin_types_compatible_p(typeof(v), type)) + v ) #define CONF_END() { .type = CONFIG_END } #define CONF_STR(k,v,d) \ { .type = CONFIG_STRING, .key = (k), .value = check_vtype(v, const char **), .defval = (d) } #define CONF_SEARCH(s, sk, sv, gk, v, d) \ { \ .type = CONFIG_SEARCH_SECTION, \ .section = (s), \ .search_key = (sk), \ .search_val = (sv), \ .get_key = (gk), \ .value = check_vtype(v, const char **), \ .defval = (d) \ } #define CONF_MONITOR(k,f) \ { .type = MONITOR_CALLBACK, .key = (k), .callback = (f)} int parse_configs_prefix(const char *config_path, const char *prefix, const struct config *configs); ndctl-81/util/parse-options.c000066400000000000000000000423421476737544500163670ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2007 Pierre Habouzit. All rights reserved. /* originally copied from perf and git */ #include #include #include #include #include #include #define OPT_SHORT 1 #define OPT_UNSET 2 static int opterror(const struct option *opt, const char *reason, int flags) { if (flags & OPT_SHORT) return error("switch `%c' %s", opt->short_name, reason); if (flags & OPT_UNSET) return error("option `no-%s' %s", opt->long_name, reason); return error("option `%s' %s", opt->long_name, reason); } static int get_arg(struct parse_opt_ctx_t *p, const struct option *opt, int flags, const char **arg) { if (p->opt) { *arg = p->opt; p->opt = NULL; } else if ((opt->flags & PARSE_OPT_LASTARG_DEFAULT) && (p->argc == 1 || **(p->argv + 1) == '-')) { *arg = (const char *)opt->defval; } else if (p->argc > 1) { p->argc--; *arg = *++p->argv; } else return opterror(opt, "requires a value", flags); return 0; } static int get_value(struct parse_opt_ctx_t *p, const struct option *opt, int flags) { const char *s, *arg = NULL; const int unset = flags & OPT_UNSET; int err; if (unset && p->opt) return opterror(opt, "takes no value", flags); if (unset && (opt->flags & PARSE_OPT_NONEG)) return opterror(opt, "isn't available", flags); if (!(flags & OPT_SHORT) && p->opt) { switch (opt->type) { case OPTION_CALLBACK: if (!(opt->flags & PARSE_OPT_NOARG)) break; /* FALLTHROUGH */ case OPTION_BOOLEAN: case OPTION_INCR: case OPTION_BIT: case OPTION_SET_UINT: case OPTION_SET_PTR: return opterror(opt, "takes no value", flags); case OPTION_END: case OPTION_ARGUMENT: case OPTION_GROUP: case OPTION_STRING: case OPTION_FILENAME: case OPTION_INTEGER: case OPTION_UINTEGER: case OPTION_LONG: case OPTION_U64: default: break; } } switch (opt->type) { case OPTION_BIT: if (unset) *(int *)opt->value &= ~opt->defval; else *(int *)opt->value |= opt->defval; return 0; case OPTION_BOOLEAN: *(bool *)opt->value = unset ? false : true; if (opt->set) *(bool *)opt->set = true; return 0; case OPTION_INCR: *(int *)opt->value = unset ? 0 : *(int *)opt->value + 1; return 0; case OPTION_SET_UINT: *(unsigned int *)opt->value = unset ? 0 : opt->defval; return 0; case OPTION_SET_PTR: *(void **)opt->value = unset ? NULL : (void *)opt->defval; return 0; case OPTION_STRING: if (unset) *(const char **)opt->value = NULL; else if (opt->flags & PARSE_OPT_OPTARG && !p->opt) *(const char **)opt->value = (const char *)opt->defval; else return get_arg(p, opt, flags, (const char **)opt->value); return 0; case OPTION_FILENAME: err = 0; if (unset) *(const char **)opt->value = NULL; else if (opt->flags & PARSE_OPT_OPTARG && !p->opt) *(const char **)opt->value = (const char *)opt->defval; else err = get_arg(p, opt, flags, (const char **)opt->value); if (!err) fix_filename(p->prefix, (const char **)opt->value); return err; case OPTION_CALLBACK: if (unset) return (*opt->callback)(opt, NULL, 1) ? (-1) : 0; if (opt->flags & PARSE_OPT_NOARG) return (*opt->callback)(opt, NULL, 0) ? (-1) : 0; if (opt->flags & PARSE_OPT_OPTARG && !p->opt) return (*opt->callback)(opt, NULL, 0) ? (-1) : 0; if (get_arg(p, opt, flags, &arg)) return -1; return (*opt->callback)(opt, arg, 0) ? (-1) : 0; case OPTION_INTEGER: if (unset) { *(int *)opt->value = 0; return 0; } if (opt->flags & PARSE_OPT_OPTARG && !p->opt) { *(int *)opt->value = opt->defval; return 0; } if (get_arg(p, opt, flags, &arg)) return -1; *(int *)opt->value = strtol(arg, (char **)&s, 10); if (*s) return opterror(opt, "expects a numerical value", flags); return 0; case OPTION_UINTEGER: if (unset) { *(unsigned int *)opt->value = 0; return 0; } if (opt->flags & PARSE_OPT_OPTARG && !p->opt) { *(unsigned int *)opt->value = opt->defval; return 0; } if (get_arg(p, opt, flags, &arg)) return -1; *(unsigned int *)opt->value = strtol(arg, (char **)&s, 10); if (*s) return opterror(opt, "expects a numerical value", flags); return 0; case OPTION_LONG: if (unset) { *(long *)opt->value = 0; return 0; } if (opt->flags & PARSE_OPT_OPTARG && !p->opt) { *(long *)opt->value = opt->defval; return 0; } if (get_arg(p, opt, flags, &arg)) return -1; *(long *)opt->value = strtol(arg, (char **)&s, 10); if (*s) return opterror(opt, "expects a numerical value", flags); return 0; case OPTION_U64: if (unset) { *(uint64_t *)opt->value = 0; return 0; } if (opt->flags & PARSE_OPT_OPTARG && !p->opt) { *(uint64_t *)opt->value = opt->defval; return 0; } if (get_arg(p, opt, flags, &arg)) return -1; *(uint64_t *)opt->value = strtoull(arg, (char **)&s, 10); if (*s) return opterror(opt, "expects a numerical value", flags); return 0; case OPTION_END: case OPTION_ARGUMENT: case OPTION_GROUP: default: die("should not happen, someone must be hit on the forehead"); } } static int parse_short_opt(struct parse_opt_ctx_t *p, const struct option *options) { for (; options->type != OPTION_END; options++) { if (options->short_name == *p->opt) { p->opt = p->opt[1] ? p->opt + 1 : NULL; return get_value(p, options, OPT_SHORT); } } return -2; } static int parse_long_opt(struct parse_opt_ctx_t *p, const char *arg, const struct option *options) { const char *arg_end = strchr(arg, '='); const struct option *abbrev_option = NULL, *ambiguous_option = NULL; int abbrev_flags = 0, ambiguous_flags = 0; if (!arg_end) arg_end = arg + strlen(arg); for (; options->type != OPTION_END; options++) { const char *rest; int flags = 0; if (!options->long_name) continue; rest = skip_prefix(arg, options->long_name); if (options->type == OPTION_ARGUMENT) { if (!rest) continue; if (*rest == '=') return opterror(options, "takes no value", flags); if (*rest) continue; p->out[p->cpidx++] = arg - 2; return 0; } if (!rest) { if (!prefixcmp(options->long_name, "no-")) { /* * The long name itself starts with "no-", so * accept the option without "no-" so that users * do not have to enter "no-no-" to get the * negation. */ rest = skip_prefix(arg, options->long_name + 3); if (rest) { flags |= OPT_UNSET; goto match; } /* Abbreviated case */ if (!prefixcmp(options->long_name + 3, arg)) { flags |= OPT_UNSET; goto is_abbreviated; } } /* abbreviated? */ if (!strncmp(options->long_name, arg, arg_end - arg)) { is_abbreviated: if (abbrev_option) { /* * If this is abbreviated, it is * ambiguous. So when there is no * exact match later, we need to * error out. */ ambiguous_option = abbrev_option; ambiguous_flags = abbrev_flags; } if (!(flags & OPT_UNSET) && *arg_end) p->opt = arg_end + 1; abbrev_option = options; abbrev_flags = flags; continue; } /* negated and abbreviated very much? */ if (!prefixcmp("no-", arg)) { flags |= OPT_UNSET; goto is_abbreviated; } /* negated? */ if (strncmp(arg, "no-", 3)) continue; flags |= OPT_UNSET; rest = skip_prefix(arg + 3, options->long_name); /* abbreviated and negated? */ if (!rest && !prefixcmp(options->long_name, arg + 3)) goto is_abbreviated; if (!rest) continue; } match: if (*rest) { if (*rest != '=') continue; p->opt = rest + 1; } return get_value(p, options, flags); } if (ambiguous_option) return error("Ambiguous option: %s " "(could be --%s%s or --%s%s)", arg, (ambiguous_flags & OPT_UNSET) ? "no-" : "", ambiguous_option->long_name, (abbrev_flags & OPT_UNSET) ? "no-" : "", abbrev_option->long_name); if (abbrev_option) return get_value(p, abbrev_option, abbrev_flags); return -2; } static void check_typos(const char *arg, const struct option *options) { if (strlen(arg) < 3) return; if (!prefixcmp(arg, "no-")) { error ("did you mean `--%s` (with two dashes ?)", arg); exit(129); } for (; options->type != OPTION_END; options++) { if (!options->long_name) continue; if (!prefixcmp(options->long_name, arg)) { error ("did you mean `--%s` (with two dashes ?)", arg); exit(129); } } } void parse_options_start(struct parse_opt_ctx_t *ctx, int argc, const char **argv, const char *prefix, int flags) { memset(ctx, 0, sizeof(*ctx)); ctx->argc = argc - 1; ctx->argv = argv + 1; ctx->out = argv; ctx->prefix = prefix; ctx->cpidx = ((flags & PARSE_OPT_KEEP_ARGV0) != 0); ctx->flags = flags; if ((flags & PARSE_OPT_KEEP_UNKNOWN) && (flags & PARSE_OPT_STOP_AT_NON_OPTION)) die("STOP_AT_NON_OPTION and KEEP_UNKNOWN don't go together"); } static int usage_with_options_internal(const char * const *, const struct option *, int); int parse_options_step(struct parse_opt_ctx_t *ctx, const struct option *options, const char * const usagestr[]) { int internal_help = !(ctx->flags & PARSE_OPT_NO_INTERNAL_HELP); /* we must reset ->opt, unknown short option leave it dangling */ ctx->opt = NULL; for (; ctx->argc; ctx->argc--, ctx->argv++) { const char *arg = ctx->argv[0]; if (*arg != '-' || !arg[1]) { if (ctx->flags & PARSE_OPT_STOP_AT_NON_OPTION) break; ctx->out[ctx->cpidx++] = ctx->argv[0]; continue; } if (arg[1] != '-') { ctx->opt = arg + 1; if (internal_help && *ctx->opt == 'h') return usage_with_options_internal(usagestr, options, 0); switch (parse_short_opt(ctx, options)) { case -1: return parse_options_usage(usagestr, options, arg + 1, 1); case -2: goto unknown; default: break; } if (ctx->opt) check_typos(arg + 1, options); while (ctx->opt) { if (internal_help && *ctx->opt == 'h') return usage_with_options_internal(usagestr, options, 0); arg = ctx->opt; switch (parse_short_opt(ctx, options)) { case -1: return parse_options_usage(usagestr, options, arg, 1); case -2: /* fake a short option thing to hide the fact that we may have * started to parse aggregated stuff * * This is leaky, too bad. */ ctx->argv[0] = strdup(ctx->opt - 1); *(char *)ctx->argv[0] = '-'; goto unknown; default: break; } } continue; } if (!arg[2]) { /* "--" */ if (!(ctx->flags & PARSE_OPT_KEEP_DASHDASH)) { ctx->argc--; ctx->argv++; } break; } if (internal_help && !strcmp(arg + 2, "help-all")) return usage_with_options_internal(usagestr, options, 1); if (internal_help && !strcmp(arg + 2, "help")) return usage_with_options_internal(usagestr, options, 0); if (!strcmp(arg + 2, "list-opts")) return PARSE_OPT_LIST_OPTS; if (!strcmp(arg + 2, "list-cmds")) return PARSE_OPT_LIST_SUBCMDS; switch (parse_long_opt(ctx, arg + 2, options)) { case -1: return parse_options_usage(usagestr, options, arg + 2, 0); case -2: goto unknown; default: break; } continue; unknown: if (!(ctx->flags & PARSE_OPT_KEEP_UNKNOWN)) return PARSE_OPT_UNKNOWN; ctx->out[ctx->cpidx++] = ctx->argv[0]; ctx->opt = NULL; } return PARSE_OPT_DONE; } int parse_options_end(struct parse_opt_ctx_t *ctx) { memmove(ctx->out + ctx->cpidx, ctx->argv, ctx->argc * sizeof(*ctx->out)); ctx->out[ctx->cpidx + ctx->argc] = NULL; return ctx->cpidx + ctx->argc; } static int parse_options_subcommand_prefix(int argc, const char **argv, const char *prefix, const struct option *options, const char *const subcommands[], const char *usagestr[], int flags) { struct parse_opt_ctx_t ctx; /* build usage string if it's not provided */ if (subcommands && !usagestr[0]) { struct strbuf buf = STRBUF_INIT; strbuf_addf(&buf, "ndctl %s [] {", argv[0]); for (int i = 0; subcommands[i]; i++) { if (i) strbuf_addstr(&buf, "|"); strbuf_addstr(&buf, subcommands[i]); } strbuf_addstr(&buf, "}"); usagestr[0] = strdup(buf.buf); strbuf_release(&buf); } parse_options_start(&ctx, argc, argv, prefix, flags); switch (parse_options_step(&ctx, options, usagestr)) { case PARSE_OPT_HELP: exit(129); case PARSE_OPT_DONE: break; case PARSE_OPT_LIST_OPTS: while (options->type != OPTION_END) { printf("--%s ", options->long_name); options++; } exit(130); case PARSE_OPT_LIST_SUBCMDS: if (subcommands) for (int i = 0; subcommands[i]; i++) printf("%s ", subcommands[i]); exit(130); default: /* PARSE_OPT_UNKNOWN */ if (ctx.argv[0][1] == '-') { error("unknown option `%s'", ctx.argv[0] + 2); } else { error("unknown switch `%c'", *ctx.opt); } usage_with_options(usagestr, options); } return parse_options_end(&ctx); } int parse_options_subcommand(int argc, const char **argv, const struct option *options, const char *const subcommands[], const char *usagestr[], int flags) { return parse_options_subcommand_prefix(argc, argv, NULL, options, subcommands, usagestr, flags); } int parse_options_prefix(int argc, const char **argv, const char *prefix, const struct option *options, const char * const usagestr[], int flags) { return parse_options_subcommand_prefix(argc, argv, prefix, options, NULL, (const char **) usagestr, flags); } int parse_options(int argc, const char **argv, const struct option *options, const char * const usagestr[], int flags) { return parse_options_subcommand_prefix(argc, argv, NULL, options, NULL, (const char **) usagestr, flags); } #define USAGE_OPTS_WIDTH 24 #define USAGE_GAP 2 static void print_option_help(const struct option *opts, int full) { size_t pos; int pad; if (opts->type == OPTION_GROUP) { fputc('\n', stderr); if (*opts->help) fprintf(stderr, "%s\n", opts->help); return; } if (!full && (opts->flags & PARSE_OPT_HIDDEN)) return; pos = fprintf(stderr, " "); if (opts->short_name) pos += fprintf(stderr, "-%c", opts->short_name); else pos += fprintf(stderr, " "); if (opts->long_name && opts->short_name) pos += fprintf(stderr, ", "); if (opts->long_name) pos += fprintf(stderr, "--%s", opts->long_name); switch (opts->type) { case OPTION_ARGUMENT: break; case OPTION_LONG: case OPTION_U64: case OPTION_INTEGER: case OPTION_UINTEGER: if (opts->flags & PARSE_OPT_OPTARG) if (opts->long_name) pos += fprintf(stderr, "[=]"); else pos += fprintf(stderr, "[]"); else pos += fprintf(stderr, " "); break; case OPTION_CALLBACK: if (opts->flags & PARSE_OPT_NOARG) break; /* FALLTHROUGH */ case OPTION_FILENAME: case OPTION_STRING: if (opts->argh) { if (opts->flags & PARSE_OPT_OPTARG) if (opts->long_name) pos += fprintf(stderr, "[=<%s>]", opts->argh); else pos += fprintf(stderr, "[<%s>]", opts->argh); else pos += fprintf(stderr, " <%s>", opts->argh); } else { if (opts->flags & PARSE_OPT_OPTARG) if (opts->long_name) pos += fprintf(stderr, "[=...]"); else pos += fprintf(stderr, "[...]"); else pos += fprintf(stderr, " ..."); } break; default: /* OPTION_{BIT,BOOLEAN,SET_UINT,SET_PTR} */ case OPTION_END: case OPTION_GROUP: case OPTION_BIT: case OPTION_BOOLEAN: case OPTION_INCR: case OPTION_SET_UINT: case OPTION_SET_PTR: break; } if (pos <= USAGE_OPTS_WIDTH) pad = USAGE_OPTS_WIDTH - pos; else { fputc('\n', stderr); pad = USAGE_OPTS_WIDTH; } fprintf(stderr, "%*s%s\n", pad + USAGE_GAP, "", opts->help); } int usage_with_options_internal(const char * const *usagestr, const struct option *opts, int full) { if (!usagestr) return PARSE_OPT_HELP; fprintf(stderr, "\n usage: %s\n", *usagestr++); while (*usagestr && **usagestr) fprintf(stderr, " or: %s\n", *usagestr++); while (*usagestr) { fprintf(stderr, "%s%s\n", **usagestr ? " " : "", *usagestr); usagestr++; } if (opts->type != OPTION_GROUP) fputc('\n', stderr); for ( ; opts->type != OPTION_END; opts++) print_option_help(opts, full); fputc('\n', stderr); return PARSE_OPT_HELP; } void usage_with_options(const char * const *usagestr, const struct option *opts) { usage_with_options_internal(usagestr, opts, 0); exit(129); } int parse_options_usage(const char * const *usagestr, const struct option *opts, const char *optstr, bool short_opt) { if (!usagestr) goto opt; fprintf(stderr, "\n usage: %s\n", *usagestr++); while (*usagestr && **usagestr) fprintf(stderr, " or: %s\n", *usagestr++); while (*usagestr) { fprintf(stderr, "%s%s\n", **usagestr ? " " : "", *usagestr); usagestr++; } fputc('\n', stderr); opt: for ( ; opts->type != OPTION_END; opts++) { if (short_opt) { if (opts->short_name == *optstr) break; continue; } if (opts->long_name == NULL) continue; if (!prefixcmp(optstr, opts->long_name)) break; if (!prefixcmp(optstr, "no-") && !prefixcmp(optstr + 3, opts->long_name)) break; } if (opts->type != OPTION_END) print_option_help(opts, 0); return PARSE_OPT_HELP; } int parse_opt_verbosity_cb(const struct option *opt, const char *arg __maybe_unused, int unset) { int *target = opt->value; if (unset) /* --no-quiet, --no-verbose */ *target = 0; else if (opt->short_name == 'v') { if (*target >= 0) (*target)++; else *target = 1; } else { if (*target <= 0) (*target)--; else *target = -1; } return 0; } ndctl-81/util/parse-options.h000066400000000000000000000207541476737544500163770ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (C) 2007 Pierre Habouzit. All rights reserved. */ /* originally copied from perf and git */ #ifndef __NDCTL_PARSE_OPTIONS_H #define __NDCTL_PARSE_OPTIONS_H #include #include #include enum parse_opt_type { /* special types */ OPTION_END, OPTION_ARGUMENT, OPTION_GROUP, /* options with no arguments */ OPTION_BIT, OPTION_BOOLEAN, OPTION_INCR, OPTION_SET_UINT, OPTION_SET_PTR, /* options with arguments (usually) */ OPTION_STRING, OPTION_INTEGER, OPTION_LONG, OPTION_CALLBACK, OPTION_U64, OPTION_UINTEGER, OPTION_FILENAME, }; enum parse_opt_flags { PARSE_OPT_KEEP_DASHDASH = 1, PARSE_OPT_STOP_AT_NON_OPTION = 2, PARSE_OPT_KEEP_ARGV0 = 4, PARSE_OPT_KEEP_UNKNOWN = 8, PARSE_OPT_NO_INTERNAL_HELP = 16, }; enum parse_opt_option_flags { PARSE_OPT_OPTARG = 1, PARSE_OPT_NOARG = 2, PARSE_OPT_NONEG = 4, PARSE_OPT_HIDDEN = 8, PARSE_OPT_LASTARG_DEFAULT = 16, }; struct option; typedef int parse_opt_cb(const struct option *, const char *arg, int unset); /* * `type`:: * holds the type of the option, you must have an OPTION_END last in your * array. * * `short_name`:: * the character to use as a short option name, '\0' if none. * * `long_name`:: * the long option name, without the leading dashes, NULL if none. * * `value`:: * stores pointers to the values to be filled. * * `argh`:: * token to explain the kind of argument this option wants. Keep it * homogenous across the repository. * * `help`:: * the short help associated to what the option does. * Must never be NULL (except for OPTION_END). * OPTION_GROUP uses this pointer to store the group header. * * `flags`:: * mask of parse_opt_option_flags. * PARSE_OPT_OPTARG: says that the argument is optionnal (not for BOOLEANs) * PARSE_OPT_NOARG: says that this option takes no argument, for CALLBACKs * PARSE_OPT_NONEG: says that this option cannot be negated * PARSE_OPT_HIDDEN this option is skipped in the default usage, showed in * the long one. * * `callback`:: * pointer to the callback to use for OPTION_CALLBACK. * * `defval`:: * default value to fill (*->value) with for PARSE_OPT_OPTARG. * OPTION_{BIT,SET_UINT,SET_PTR} store the {mask,integer,pointer} to put in * the value when met. * CALLBACKS can use it like they want. * * `set`:: * whether an option was set by the user */ struct option { enum parse_opt_type type; int short_name; const char *long_name; void *value; const char *argh; const char *help; int flags; parse_opt_cb *callback; intptr_t defval; bool *set; }; #define check_vtype(v, type) ( BUILD_BUG_ON_ZERO(!__builtin_types_compatible_p(typeof(v), type)) + v ) #define OPT_END() { .type = OPTION_END } #define OPT_ARGUMENT(l, h) { .type = OPTION_ARGUMENT, .long_name = (l), .help = (h) } #define OPT_GROUP(h) { .type = OPTION_GROUP, .help = (h) } #define OPT_BIT(s, l, v, h, b) { .type = OPTION_BIT, .short_name = (s), .long_name = (l), .value = check_vtype(v, int *), .help = (h), .defval = (b) } #define OPT_BOOLEAN(s, l, v, h) { .type = OPTION_BOOLEAN, .short_name = (s), .long_name = (l), .value = check_vtype(v, bool *), .help = (h) } #define OPT_BOOLEAN_SET(s, l, v, os, h) \ { .type = OPTION_BOOLEAN, .short_name = (s), .long_name = (l), \ .value = check_vtype(v, bool *), .help = (h), \ .set = check_vtype(os, bool *)} #define OPT_INCR(s, l, v, h) { .type = OPTION_INCR, .short_name = (s), .long_name = (l), .value = check_vtype(v, int *), .help = (h) } #define OPT_SET_UINT(s, l, v, h, i) { .type = OPTION_SET_UINT, .short_name = (s), .long_name = (l), .value = check_vtype(v, unsigned int *), .help = (h), .defval = (i) } #define OPT_SET_PTR(s, l, v, h, p) { .type = OPTION_SET_PTR, .short_name = (s), .long_name = (l), .value = (v), .help = (h), .defval = (p) } #define OPT_INTEGER(s, l, v, h) { .type = OPTION_INTEGER, .short_name = (s), .long_name = (l), .value = check_vtype(v, int *), .help = (h) } #define OPT_UINTEGER(s, l, v, h) { .type = OPTION_UINTEGER, .short_name = (s), .long_name = (l), .value = check_vtype(v, unsigned int *), .help = (h) } #define OPT_LONG(s, l, v, h) { .type = OPTION_LONG, .short_name = (s), .long_name = (l), .value = check_vtype(v, long *), .help = (h) } #define OPT_U64(s, l, v, h) { .type = OPTION_U64, .short_name = (s), .long_name = (l), .value = check_vtype(v, u64 *), .help = (h) } #define OPT_STRING(s, l, v, a, h) { .type = OPTION_STRING, .short_name = (s), .long_name = (l), .value = check_vtype(v, const char **), (a), .help = (h) } #define OPT_FILENAME(s, l, v, a, h) { .type = OPTION_FILENAME, .short_name = (s), .long_name = (l), .value = check_vtype(v, const char **), (a), .help = (h) } #define OPT_DATE(s, l, v, h) \ { .type = OPTION_CALLBACK, .short_name = (s), .long_name = (l), .value = (v), .argh = "time", .help = (h), .callback = parse_opt_approxidate_cb } #define OPT_CALLBACK(s, l, v, a, h, f) \ { .type = OPTION_CALLBACK, .short_name = (s), .long_name = (l), .value = (v), (a), .help = (h), .callback = (f) } #define OPT_CALLBACK_NOOPT(s, l, v, a, h, f) \ { .type = OPTION_CALLBACK, .short_name = (s), .long_name = (l), .value = (v), (a), .help = (h), .callback = (f), .flags = PARSE_OPT_NOARG } #define OPT_CALLBACK_DEFAULT(s, l, v, a, h, f, d) \ { .type = OPTION_CALLBACK, .short_name = (s), .long_name = (l), .value = (v), (a), .help = (h), .callback = (f), .defval = (intptr_t)d, .flags = PARSE_OPT_LASTARG_DEFAULT } #define OPT_CALLBACK_DEFAULT_NOOPT(s, l, v, a, h, f, d) \ { .type = OPTION_CALLBACK, .short_name = (s), .long_name = (l),\ .value = (v), (a), .help = (h), .callback = (f), .defval = (intptr_t)d,\ .flags = PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NOARG} /* parse_options() will filter out the processed options and leave the * non-option argments in argv[]. * Returns the number of arguments left in argv[]. */ extern int parse_options(int argc, const char **argv, const struct option *options, const char * const usagestr[], int flags); extern int parse_options_prefix(int argc, const char **argv, const char *prefix, const struct option *options, const char * const usagestr[], int flags); extern int parse_options_subcommand(int argc, const char **argv, const struct option *options, const char *const subcommands[], const char *usagestr[], int flags); extern NORETURN void usage_with_options(const char * const *usagestr, const struct option *options); /*----- incremantal advanced APIs -----*/ enum { PARSE_OPT_HELP = -1, PARSE_OPT_DONE, PARSE_OPT_LIST_OPTS, PARSE_OPT_LIST_SUBCMDS, PARSE_OPT_UNKNOWN, }; /* * It's okay for the caller to consume argv/argc in the usual way. * Other fields of that structure are private to parse-options and should not * be modified in any way. */ struct parse_opt_ctx_t { const char **argv; const char **out; int argc, cpidx; const char *opt; int flags; const char *prefix; }; extern int parse_options_usage(const char * const *usagestr, const struct option *opts, const char *optstr, bool short_opt); extern void parse_options_start(struct parse_opt_ctx_t *ctx, int argc, const char **argv, const char *prefix, int flags); extern int parse_options_step(struct parse_opt_ctx_t *ctx, const struct option *options, const char * const usagestr[]); extern int parse_options_end(struct parse_opt_ctx_t *ctx); /*----- some often used options -----*/ extern int parse_opt_abbrev_cb(const struct option *, const char *, int); extern int parse_opt_approxidate_cb(const struct option *, const char *, int); extern int parse_opt_verbosity_cb(const struct option *, const char *, int); #define OPT__VERBOSE(var) OPT_BOOLEAN('v', "verbose", (var), "be verbose") #define OPT__QUIET(var) OPT_BOOLEAN('q', "quiet", (var), "be quiet") #define OPT__VERBOSITY(var) \ { OPTION_CALLBACK, 'v', "verbose", (var), NULL, "be more verbose", \ PARSE_OPT_NOARG, &parse_opt_verbosity_cb, 0 }, \ { OPTION_CALLBACK, 'q', "quiet", (var), NULL, "be more quiet", \ PARSE_OPT_NOARG, &parse_opt_verbosity_cb, 0 } #define OPT__DRY_RUN(var) OPT_BOOLEAN('n', "dry-run", (var), "dry run") #define OPT__ABBREV(var) \ { OPTION_CALLBACK, 0, "abbrev", (var), "n", \ "use digits to display SHA-1s", \ PARSE_OPT_OPTARG, &parse_opt_abbrev_cb, 0 } extern const char *parse_options_fix_filename(const char *prefix, const char *file); #endif /* __NDCTL_PARSE_OPTIONS_H */ ndctl-81/util/size.c000066400000000000000000000017601476737544500145350ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2015-2020 Intel Corporation. All rights reserved. #include #include #include unsigned long long __parse_size64(const char *str, unsigned long long *units) { unsigned long long val, check; char *end; val = strtoull(str, &end, 0); if (val == ULLONG_MAX) return val; check = val; switch (*end) { case 'k': case 'K': if (units) *units = SZ_1K; val *= SZ_1K; end++; break; case 'm': case 'M': if (units) *units = SZ_1M; val *= SZ_1M; end++; break; case 'g': case 'G': if (units) *units = SZ_1G; val *= SZ_1G; end++; break; case 't': case 'T': if (units) *units = SZ_1T; val *= SZ_1T; end++; break; default: if (units) *units = 1; break; } if (val < check || *end != '\0') val = ULLONG_MAX; return val; } unsigned long long parse_size64(const char *str) { if (!str) return 0; return __parse_size64(str, NULL); } ndctl-81/util/size.h000066400000000000000000000170051476737544500145410ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (C) 2015-2020 Intel Corporation. All rights reserved. */ #ifndef _NDCTL_SIZE_H_ #define _NDCTL_SIZE_H_ #include #include #include #include #define SZ_1K 0x00000400 #define SZ_4K 0x00001000 #define SZ_8K 0x00002000 #define SZ_1M 0x00100000 #define SZ_2M 0x00200000 #define SZ_4M 0x00400000 #define SZ_16M 0x01000000 #define SZ_64M 0x04000000 #define SZ_256M 0x10000000 #define SZ_1G 0x40000000 #define SZ_1T 0x10000000000ULL unsigned long long parse_size64(const char *str); unsigned long long __parse_size64(const char *str, unsigned long long *units); static inline bool is_power_of_2(unsigned long long v) { return v && ((v & (v - 1)) == 0); } #define ALIGN(x, a) ((((unsigned long long) x) + (a - 1)) & ~(a - 1)) #define ALIGN_DOWN(x, a) (((((unsigned long long) x) + a) & ~(a - 1)) - a) #define IS_ALIGNED(x, a) (((x) & ((typeof(x))(a) - 1)) == 0) #define BITS_PER_LONG (sizeof(unsigned long) * 8) #define HPAGE_SIZE (2 << 20) /* * Helpers for struct_size() copied from include/linux/overflow.h (GPL-2.0) * * For simplicity and code hygiene, the fallback code below insists on * a, b and *d having the same type (similar to the min() and max() * macros), whereas gcc's type-generic overflow checkers accept * different types. Hence we don't just make check_add_overflow an * alias for __builtin_add_overflow, but add type checks similar to * below. */ #define is_signed_type(type) (((type)(-1)) < (type)1) #define __type_half_max(type) ((type)1 << (8*sizeof(type) - 1 - is_signed_type(type))) #define type_max(T) ((T)((__type_half_max(T) - 1) + __type_half_max(T))) #define type_min(T) ((T)((T)-type_max(T)-(T)1)) #if GCC_VERSION >= 50100 #define COMPILER_HAS_GENERIC_BUILTIN_OVERFLOW 1 #endif #if __clang__ #if __has_builtin(__builtin_mul_overflow) && \ __has_builtin(__builtin_add_overflow) #define COMPILER_HAS_GENERIC_BUILTIN_OVERFLOW 1 #endif #endif #if COMPILER_HAS_GENERIC_BUILTIN_OVERFLOW #define check_add_overflow(a, b, d) ({ \ typeof(a) __a = (a); \ typeof(b) __b = (b); \ typeof(d) __d = (d); \ (void) (&__a == &__b); \ (void) (&__a == __d); \ __builtin_add_overflow(__a, __b, __d); \ }) #define check_sub_overflow(a, b, d) ({ \ typeof(a) __a = (a); \ typeof(b) __b = (b); \ typeof(d) __d = (d); \ (void) (&__a == &__b); \ (void) (&__a == __d); \ __builtin_sub_overflow(__a, __b, __d); \ }) #define check_mul_overflow(a, b, d) ({ \ typeof(a) __a = (a); \ typeof(b) __b = (b); \ typeof(d) __d = (d); \ (void) (&__a == &__b); \ (void) (&__a == __d); \ __builtin_mul_overflow(__a, __b, __d); \ }) #else /* !COMPILER_HAS_GENERIC_BUILTIN_OVERFLOW */ /* Checking for unsigned overflow is relatively easy without causing UB. */ #define __unsigned_add_overflow(a, b, d) ({ \ typeof(a) __a = (a); \ typeof(b) __b = (b); \ typeof(d) __d = (d); \ (void) (&__a == &__b); \ (void) (&__a == __d); \ *__d = __a + __b; \ *__d < __a; \ }) #define __unsigned_sub_overflow(a, b, d) ({ \ typeof(a) __a = (a); \ typeof(b) __b = (b); \ typeof(d) __d = (d); \ (void) (&__a == &__b); \ (void) (&__a == __d); \ *__d = __a - __b; \ __a < __b; \ }) /* * If one of a or b is a compile-time constant, this avoids a division. */ #define __unsigned_mul_overflow(a, b, d) ({ \ typeof(a) __a = (a); \ typeof(b) __b = (b); \ typeof(d) __d = (d); \ (void) (&__a == &__b); \ (void) (&__a == __d); \ *__d = __a * __b; \ __builtin_constant_p(__b) ? \ __b > 0 && __a > type_max(typeof(__a)) / __b : \ __a > 0 && __b > type_max(typeof(__b)) / __a; \ }) /* * For signed types, detecting overflow is much harder, especially if * we want to avoid UB. But the interface of these macros is such that * we must provide a result in *d, and in fact we must produce the * result promised by gcc's builtins, which is simply the possibly * wrapped-around value. Fortunately, we can just formally do the * operations in the widest relevant unsigned type (u64) and then * truncate the result - gcc is smart enough to generate the same code * with and without the (u64) casts. */ /* * Adding two signed integers can overflow only if they have the same * sign, and overflow has happened iff the result has the opposite * sign. */ #define __signed_add_overflow(a, b, d) ({ \ typeof(a) __a = (a); \ typeof(b) __b = (b); \ typeof(d) __d = (d); \ (void) (&__a == &__b); \ (void) (&__a == __d); \ *__d = (u64)__a + (u64)__b; \ (((~(__a ^ __b)) & (*__d ^ __a)) \ & type_min(typeof(__a))) != 0; \ }) /* * Subtraction is similar, except that overflow can now happen only * when the signs are opposite. In this case, overflow has happened if * the result has the opposite sign of a. */ #define __signed_sub_overflow(a, b, d) ({ \ typeof(a) __a = (a); \ typeof(b) __b = (b); \ typeof(d) __d = (d); \ (void) (&__a == &__b); \ (void) (&__a == __d); \ *__d = (u64)__a - (u64)__b; \ ((((__a ^ __b)) & (*__d ^ __a)) \ & type_min(typeof(__a))) != 0; \ }) /* * Signed multiplication is rather hard. gcc always follows C99, so * division is truncated towards 0. This means that we can write the * overflow check like this: * * (a > 0 && (b > MAX/a || b < MIN/a)) || * (a < -1 && (b > MIN/a || b < MAX/a) || * (a == -1 && b == MIN) * * The redundant casts of -1 are to silence an annoying -Wtype-limits * (included in -Wextra) warning: When the type is u8 or u16, the * __b_c_e in check_mul_overflow obviously selects * __unsigned_mul_overflow, but unfortunately gcc still parses this * code and warns about the limited range of __b. */ #define __signed_mul_overflow(a, b, d) ({ \ typeof(a) __a = (a); \ typeof(b) __b = (b); \ typeof(d) __d = (d); \ typeof(a) __tmax = type_max(typeof(a)); \ typeof(a) __tmin = type_min(typeof(a)); \ (void) (&__a == &__b); \ (void) (&__a == __d); \ *__d = (u64)__a * (u64)__b; \ (__b > 0 && (__a > __tmax/__b || __a < __tmin/__b)) || \ (__b < (typeof(__b))-1 && (__a > __tmin/__b || __a < __tmax/__b)) || \ (__b == (typeof(__b))-1 && __a == __tmin); \ }) #define check_add_overflow(a, b, d) \ __builtin_choose_expr(is_signed_type(typeof(a)), \ __signed_add_overflow(a, b, d), \ __unsigned_add_overflow(a, b, d)) #define check_sub_overflow(a, b, d) \ __builtin_choose_expr(is_signed_type(typeof(a)), \ __signed_sub_overflow(a, b, d), \ __unsigned_sub_overflow(a, b, d)) #define check_mul_overflow(a, b, d) \ __builtin_choose_expr(is_signed_type(typeof(a)), \ __signed_mul_overflow(a, b, d), \ __unsigned_mul_overflow(a, b, d)) #endif /* * Compute a*b+c, returning SIZE_MAX on overflow. Internal helper for * struct_size() below. */ static inline size_t __ab_c_size(size_t a, size_t b, size_t c) { size_t bytes; if (check_mul_overflow(a, b, &bytes)) return SIZE_MAX; if (check_add_overflow(bytes, c, &bytes)) return SIZE_MAX; return bytes; } /** * struct_size() - Calculate size of structure with trailing array. * @p: Pointer to the structure. * @member: Name of the array member. * @count: Number of elements in the array. * * Calculates size of memory needed for structure @p followed by an * array of @count number of @member elements. * * Return: number of bytes needed or SIZE_MAX on overflow. */ #define struct_size(p, member, count) \ __ab_c_size(count, \ sizeof(*(p)->member) + __must_be_array((p)->member),\ sizeof(*(p))) #endif /* _NDCTL_SIZE_H_ */ ndctl-81/util/strbuf.c000066400000000000000000000037341476737544500150730ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2005 Junio C Hamano. All rights reserved. /* originally copied from perf and git */ #include #include #include #include #include int prefixcmp(const char *str, const char *prefix) { for (; ; str++, prefix++) if (!*prefix) return 0; else if (*str != *prefix) return (unsigned char)*prefix - (unsigned char)*str; } /* * Used as the default ->buf value, so that people can always assume * buf is non NULL and ->buf is NUL terminated even for a freshly * initialized strbuf. */ char strbuf_slopbuf[1]; void strbuf_init(struct strbuf *sb, ssize_t hint) { sb->alloc = sb->len = 0; sb->buf = strbuf_slopbuf; if (hint) strbuf_grow(sb, hint); } void strbuf_release(struct strbuf *sb) { if (sb->alloc) { zfree(&sb->buf); strbuf_init(sb, 0); } } char *strbuf_detach(struct strbuf *sb, size_t *sz) { char *res = sb->alloc ? sb->buf : NULL; if (sz) *sz = sb->len; strbuf_init(sb, 0); return res; } void strbuf_grow(struct strbuf *sb, size_t extra) { if (sb->len + extra + 1 <= sb->len) die("you want to use way too much memory"); if (!sb->alloc) sb->buf = NULL; ALLOC_GROW(sb->buf, sb->len + extra + 1, sb->alloc); } void strbuf_add(struct strbuf *sb, const void *data, size_t len) { strbuf_grow(sb, len); memcpy(sb->buf + sb->len, data, len); strbuf_setlen(sb, sb->len + len); } void strbuf_addf(struct strbuf *sb, const char *fmt, ...) { int len; va_list ap; if (!strbuf_avail(sb)) strbuf_grow(sb, 64); va_start(ap, fmt); len = vsnprintf(sb->buf + sb->len, sb->alloc - sb->len, fmt, ap); va_end(ap); if (len < 0) die("your vsnprintf is broken"); if (len > strbuf_avail(sb)) { strbuf_grow(sb, len); va_start(ap, fmt); len = vsnprintf(sb->buf + sb->len, sb->alloc - sb->len, fmt, ap); va_end(ap); if (len > strbuf_avail(sb)) { die("this should not happen, your vsnprintf is broken"); } } strbuf_setlen(sb, sb->len + len); } ndctl-81/util/strbuf.h000066400000000000000000000056601476737544500151000ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (C) 2005 Junio C Hamano. All rights reserved. */ /* originally copied from perf and git */ #ifndef __NDCTL_STRBUF_H #define __NDCTL_STRBUF_H #include #include #include /* for ssize_t */ /* * Strbuf's can be use in many ways: as a byte array, or to store arbitrary * long, overflow safe strings. * * Strbufs has some invariants that are very important to keep in mind: * * 1. the ->buf member is always malloc-ed, hence strbuf's can be used to * build complex strings/buffers whose final size isn't easily known. * * It is NOT legal to copy the ->buf pointer away. * `strbuf_detach' is the operation that detachs a buffer from its shell * while keeping the shell valid wrt its invariants. * * 2. the ->buf member is a byte array that has at least ->len + 1 bytes * allocated. The extra byte is used to store a '\0', allowing the ->buf * member to be a valid C-string. Every strbuf function ensure this * invariant is preserved. * * Note that it is OK to "play" with the buffer directly if you work it * that way: * * strbuf_grow(sb, SOME_SIZE); * ... Here, the memory array starting at sb->buf, and of length * ... strbuf_avail(sb) is all yours, and you are sure that * ... strbuf_avail(sb) is at least SOME_SIZE. * strbuf_setlen(sb, sb->len + SOME_OTHER_SIZE); * * Of course, SOME_OTHER_SIZE must be smaller or equal to strbuf_avail(sb). * * Doing so is safe, though if it has to be done in many places, adding the * missing API to the strbuf module is the way to go. * * XXX: do _not_ assume that the area that is yours is of size ->alloc - 1 * even if it's true in the current implementation. Alloc is somehow a * "private" member that should not be messed with. */ extern char strbuf_slopbuf[]; struct strbuf { size_t alloc; size_t len; char *buf; }; #define STRBUF_INIT { 0, 0, strbuf_slopbuf } /*----- strbuf life cycle -----*/ extern void strbuf_release(struct strbuf *); extern char *strbuf_detach(struct strbuf *, size_t *); /*----- strbuf size related -----*/ static inline ssize_t strbuf_avail(const struct strbuf *sb) { return sb->alloc ? sb->alloc - sb->len - 1 : 0; } extern void strbuf_grow(struct strbuf *, size_t); static inline void strbuf_setlen(struct strbuf *sb, size_t len) { if (!sb->alloc) strbuf_grow(sb, 0); assert(len < sb->alloc); sb->len = len; sb->buf[len] = '\0'; } /*----- add data in your buffer -----*/ static inline void strbuf_addch(struct strbuf *sb, int c) { strbuf_grow(sb, 1); sb->buf[sb->len++] = c; sb->buf[sb->len] = '\0'; } extern void strbuf_add(struct strbuf *, const void *, size_t); static inline void strbuf_addstr(struct strbuf *sb, const char *s) { strbuf_add(sb, s, strlen(s)); } __attribute__((format(printf,2,3))) extern void strbuf_addf(struct strbuf *sb, const char *fmt, ...); #endif /* __NDCTL_STRBUF_H */ ndctl-81/util/sysfs.c000066400000000000000000000114041476737544500147260ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-2.1 // Copyright (C) 2014-2020, Intel Corporation. All rights reserved. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include int __sysfs_read_attr(struct log_ctx *ctx, const char *path, char *buf) { int fd = open(path, O_RDONLY|O_CLOEXEC); int n; if (fd < 0) { log_dbg(ctx, "failed to open %s: %s\n", path, strerror(errno)); return -errno; } n = read(fd, buf, SYSFS_ATTR_SIZE); close(fd); if (n < 0 || n >= SYSFS_ATTR_SIZE) { buf[0] = 0; log_dbg(ctx, "failed to read %s: %s\n", path, strerror(errno)); return -errno; } buf[n] = 0; if (n && buf[n-1] == '\n') buf[n-1] = 0; return 0; } static int write_attr(struct log_ctx *ctx, const char *path, const char *buf, int quiet) { int fd = open(path, O_WRONLY|O_CLOEXEC); int n, len = strlen(buf) + 1, rc; if (fd < 0) { rc = -errno; log_dbg(ctx, "failed to open %s: %s\n", path, strerror(errno)); return rc; } n = write(fd, buf, len); rc = -errno; close(fd); if (n < len) { if (!quiet) log_dbg(ctx, "failed to write %s to %s: %s\n", buf, path, strerror(errno)); return rc; } return 0; } int __sysfs_write_attr(struct log_ctx *ctx, const char *path, const char *buf) { return write_attr(ctx, path, buf, 0); } int __sysfs_write_attr_quiet(struct log_ctx *ctx, const char *path, const char *buf) { return write_attr(ctx, path, buf, 1); } int __sysfs_device_parse(struct log_ctx *ctx, const char *base_path, const char *dev_name, void *parent, add_dev_fn add_dev) { int add_errors = 0; struct dirent *de; DIR *dir; log_dbg(ctx, "base: '%s' dev: '%s'\n", base_path, dev_name); dir = opendir(base_path); if (!dir) { log_dbg(ctx, "no \"%s\" devices found\n", dev_name); return -ENODEV; } while ((de = readdir(dir)) != NULL) { char *dev_path; char fmt[20]; void *dev; int id; sprintf(fmt, "%s%%d", dev_name); if (de->d_ino == 0) continue; if (sscanf(de->d_name, fmt, &id) != 1) continue; if (asprintf(&dev_path, "%s/%s", base_path, de->d_name) < 0) { log_err(ctx, "%s%d: path allocation failure\n", dev_name, id); continue; } dev = add_dev(parent, id, dev_path); free(dev_path); if (!dev) { add_errors++; log_err(ctx, "%s%d: add_dev() failed\n", dev_name, id); } else log_dbg(ctx, "%s%d: processed\n", dev_name, id); } closedir(dir); return add_errors; } struct kmod_module *__util_modalias_to_module(struct kmod_ctx *kmod_ctx, const char *alias, struct log_ctx *log) { struct kmod_list *list = NULL; struct kmod_module *mod; int rc; if (!kmod_ctx) return NULL; rc = kmod_module_new_from_lookup(kmod_ctx, alias, &list); if (rc < 0 || !list) { log_dbg(log, "failed to find module for alias: %s %d list: %s\n", alias, rc, list ? "populated" : "empty"); return NULL; } mod = kmod_module_get_module(list); log_dbg(log, "alias: %s module: %s\n", alias, kmod_module_get_name(mod)); kmod_module_unref_list(list); return mod; } int __util_bind(const char *devname, struct kmod_module *module, const char *bus, struct log_ctx *ctx) { DIR *dir; int rc = 0; char path[200]; struct dirent *de; const int len = sizeof(path); if (!devname) { log_err(ctx, "missing devname\n"); return -EINVAL; } if (module) { rc = kmod_module_probe_insert_module(module, KMOD_PROBE_APPLY_BLACKLIST, NULL, NULL, NULL, NULL); if (rc < 0) { log_err(ctx, "%s: insert failure: %d\n", __func__, rc); return rc; } } if (snprintf(path, len, "/sys/bus/%s/drivers", bus) >= len) { log_err(ctx, "%s: buffer too small!\n", devname); return -ENXIO; } dir = opendir(path); if (!dir) { log_err(ctx, "%s: opendir(\"%s\") failed\n", devname, path); return -ENXIO; } while ((de = readdir(dir)) != NULL) { char *drv_path; if (de->d_ino == 0) continue; if (de->d_name[0] == '.') continue; if (asprintf(&drv_path, "%s/%s/bind", path, de->d_name) < 0) { log_err(ctx, "%s: path allocation failure\n", devname); continue; } rc = __sysfs_write_attr_quiet(ctx, drv_path, devname); free(drv_path); if (rc == 0) break; } closedir(dir); if (rc) { log_dbg(ctx, "%s: bind failed\n", devname); return -ENXIO; } return 0; } int __util_unbind(const char *devpath, struct log_ctx *ctx) { const char *devname = devpath_to_devname(devpath); char path[200]; const int len = sizeof(path); if (snprintf(path, len, "%s/driver/unbind", devpath) >= len) { log_err(ctx, "%s: buffer too small!\n", devname); return -ENXIO; } return __sysfs_write_attr(ctx, path, devname); } ndctl-81/util/sysfs.h000066400000000000000000000033251476737544500147360ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1 */ /* Copyright (C) 2014-2020, Intel Corporation. All rights reserved. */ #ifndef __UTIL_SYSFS_H__ #define __UTIL_SYSFS_H__ #include typedef void *(*add_dev_fn)(void *parent, int id, const char *dev_path); #define SYSFS_ATTR_SIZE 1024 struct log_ctx; int __sysfs_read_attr(struct log_ctx *ctx, const char *path, char *buf); int __sysfs_write_attr(struct log_ctx *ctx, const char *path, const char *buf); int __sysfs_write_attr_quiet(struct log_ctx *ctx, const char *path, const char *buf); int __sysfs_device_parse(struct log_ctx *ctx, const char *base_path, const char *dev_name, void *parent, add_dev_fn add_dev); #define sysfs_read_attr(c, p, b) __sysfs_read_attr(&(c)->ctx, (p), (b)) #define sysfs_write_attr(c, p, b) __sysfs_write_attr(&(c)->ctx, (p), (b)) #define sysfs_write_attr_quiet(c, p, b) __sysfs_write_attr_quiet(&(c)->ctx, (p), (b)) #define sysfs_device_parse(c, b, d, p, fn) __sysfs_device_parse(&(c)->ctx, \ (b), (d), (p), (fn)) static inline const char *devpath_to_devname(const char *devpath) { return strrchr(devpath, '/') + 1; } struct kmod_ctx; struct kmod_module; struct kmod_module *__util_modalias_to_module(struct kmod_ctx *kmod_ctx, const char *alias, struct log_ctx *log); #define util_modalias_to_module(ctx, buf) \ __util_modalias_to_module((ctx)->kmod_ctx, buf, &(ctx)->ctx) int __util_bind(const char *devname, struct kmod_module *module, const char *bus, struct log_ctx *ctx); #define util_bind(n, m, b, c) __util_bind(n, m, b, &(c)->ctx) int __util_unbind(const char *devpath, struct log_ctx *ctx); #define util_unbind(p, c) __util_unbind(p, &(c)->ctx) #endif /* __UTIL_SYSFS_H__ */ ndctl-81/util/usage.c000066400000000000000000000034121476737544500146630ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2005 Linus Torvalds. All rights reserved. /* originally copied from perf and git */ #include #include #include #include static void report(const char *prefix, const char *err, va_list params) { char msg[1024]; vsnprintf(msg, sizeof(msg), err, params); fprintf(stderr, " %s%s\n", prefix, msg); } static NORETURN void usage_builtin(const char *err) { fprintf(stderr, "\n Usage: %s\n", err); exit(129); } static NORETURN void die_builtin(const char *err, va_list params) { report(" Fatal: ", err, params); exit(128); } static void error_builtin(const char *err, va_list params) { report(" Error: ", err, params); } static void warn_builtin(const char *warn, va_list params) { report(" Warning: ", warn, params); } /* If we are in a dlopen()ed .so write to a global variable would segfault * (ugh), so keep things static. */ static void (*usage_routine)(const char *err) NORETURN = usage_builtin; static void (*die_routine)(const char *err, va_list params) NORETURN = die_builtin; static void (*error_routine)(const char *err, va_list params) = error_builtin; static void (*warn_routine)(const char *err, va_list params) = warn_builtin; void set_die_routine(void (*routine)(const char *err, va_list params) NORETURN) { die_routine = routine; } void usage(const char *err) { usage_routine(err); } void die(const char *err, ...) { va_list params; va_start(params, err); die_routine(err, params); va_end(params); } int error(const char *err, ...) { va_list params; va_start(params, err); error_routine(err, params); va_end(params); return -1; } void warning(const char *warn, ...) { va_list params; va_start(params, warn); warn_routine(warn, params); va_end(params); } ndctl-81/util/util.h000066400000000000000000000060561476737544500145500ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 */ /* Copyright (C) 2005 Junio C Hamano. All rights reserved. */ /* Copyright (C) 2005 Linus Torvalds. All rights reserved. */ /* originally copied from perf and git */ #ifndef __UTIL_H__ #define __UTIL_H__ #include #include #include #pragma GCC diagnostic ignored "-Wmissing-prototypes" #ifdef __GNUC__ #define NORETURN __attribute__((__noreturn__)) #else #define NORETURN #ifndef __attribute__ #define __attribute__(x) #endif #endif #ifndef __maybe_unused # define __maybe_unused /* unimplemented */ #endif #define is_dir_sep(c) ((c) == '/') #define alloc_nr(x) (((x)+16)*3/2) #define __round_mask(x, y) ((__typeof__(x))((y)-1)) #define round_up(x, y) ((((x)-1) | __round_mask(x, y))+1) #define round_down(x, y) ((x) & ~__round_mask(x, y)) #define rounddown(x, y) ( \ { \ typeof(x) __x = (x); \ __x - (__x % (y)); \ } \ ) /* * Realloc the buffer pointed at by variable 'x' so that it can hold * at least 'nr' entries; the number of entries currently allocated * is 'alloc', using the standard growing factor alloc_nr() macro. * * DO NOT USE any expression with side-effect for 'x' or 'alloc'. */ #define ALLOC_GROW(x, nr, alloc) \ do { \ if ((nr) > alloc) { \ if (alloc_nr(alloc) < (nr)) \ alloc = (nr); \ else \ alloc = alloc_nr(alloc); \ x = xrealloc((x), alloc * sizeof(*(x))); \ } \ } while(0) #define zfree(ptr) ({ free(*ptr); *ptr = NULL; }) #define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); })) #define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)])) /* Are two types/vars the same type (ignoring qualifiers)? */ #define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b)) /* &a[0] degrades to a pointer: a different type from an array */ #define __must_be_array(a) BUILD_BUG_ON_ZERO(__same_type((a), &(a)[0])) enum { READ, WRITE, }; static inline const char *skip_prefix(const char *str, const char *prefix) { size_t len = strlen(prefix); return strncmp(str, prefix, len) ? NULL : str + len; } static inline const char *which_sep(const char *filter) { if (strchr(filter, ' ')) return " "; if (strchr(filter, ',')) return ","; return " "; } static inline int is_absolute_path(const char *path) { return path[0] == '/'; } void usage(const char *err) NORETURN; void die(const char *err, ...) NORETURN __attribute__((format (printf, 1, 2))); int error(const char *err, ...) __attribute__((format (printf, 1, 2))); void warning(const char *err, ...) __attribute__((format (printf, 1, 2))); void set_die_routine(void (*routine)(const char *err, va_list params) NORETURN); char *xstrdup(const char *str); void *xrealloc(void *ptr, size_t size); int prefixcmp(const char *str, const char *prefix); char *prefix_filename(const char *pfx, const char *arg); void fix_filename(const char *prefix, const char **file); #endif /* __UTIL_H__ */ ndctl-81/util/wrapper.c000066400000000000000000000012651476737544500152430ustar00rootroot00000000000000// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2005 Junio C Hamano. All rights reserved. // Copyright (C) 2005 Linus Torvalds. All rights reserved. /* originally copied from perf and git */ /* * Various trivial helper wrappers around standard functions */ #include #include #include char *xstrdup(const char *str) { char *ret = strdup(str); if (!ret) { ret = strdup(str); if (!ret) die("Out of memory, strdup failed"); } return ret; } void *xrealloc(void *ptr, size_t size) { void *ret; if (!size) { free(ptr); return malloc(1); } ret = realloc(ptr, size); if (!ret) die("Out of memory, realloc failed"); return ret; } ndctl-81/version.h.in000066400000000000000000000001041476737544500146740ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1 */ #define VERSION "@VCS_TAG@"