diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4da8279 --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +ledctl.8.gz +ledmon.conf.5.gz +ledmon.8.gz +*.o +_build +ledmon +ledctl +*~ +*.bak +.depend +*.swp + +Makefile +Makefile.in +aclocal.m4 +autom4te.cache/ +config.h.in +build-aux/ +configure +config_ac.h +config.log +config.status +stamp-h1 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..ea68648 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,161 @@ +### v0.94 / 2020-02-04 + +[Commit list](https://github.com/intel/ledmon/compare/v0.93...v0.94) + +Enhancements + +* Support for AMD IPMI enclosure management +* Support for NPEM + +Bug fixes + +* Documentation updates +* Fix activity indicator state for SMP +* Fix for GCC 9 compilation +* Update ipbi pattern for drives with previous pattern unknown + +### v0.93 / 2019-10-17 + +[Commit list](https://github.com/intel/ledmon/compare/v0.92...v0.93) + +Enhancements + +* Support for AMD SGPIO enclosure management +* Migration to GNU Autotools build system +* Added more strict compilation flags + +Bug fixes + +* Fixed segfault when a value is missing from ibpi_str +* Use proper format string with syslog() +* Fixed issues reported by static analysis +* Removed unused SGPIO structures +* Added udev_device reference clean-up +* Hidden ipmi error messages on non-dell platforms + +### v0.92 / 2019-04-12 + +[Commit list](https://github.com/intel/ledmon/compare/v0.91-fixed...v0.92) + +Bug fixes +* Silence warning and error messages. + + +### v0.91 / 2019-04-01 + +[Commit list](https://github.com/intel/ledmon/compare/v0.90...v0.91) + +Enhancements + +* Ledmon systemd service file. +* Shared configuration between ledmon and ledctl. +* Log-level support for ledctl. +* Build label support. +* 13G/14G Dell Servers support. +* Foreground option. + +Bug fixes + +* Udev action handling reimplementation. +* Unify ping process method. +* Recognize volumes under reshape. +* Distinguish inactive state for volume and container. +* Fix various gcc and clang warnings. +* Fix ledctl exit status. +* Logging method reimplementation. +* Makefile fixes. +* Change outdated functions and simplify string modifications. +* Ommited errors handling. + + +### v0.90 / 2018-02-14 + +[Commit list](https://github.com/intel/ledmon/compare/v0.80...v0.90) + +Enhancements + +* Handle udev events in ledmon. +* Possibility to list all controllers detected by LED utilities tool (ledctl --list-controllers). +* Configuration file for ledmon advanced features (check man ledmon.config). +* Added option to ledctl for managing only listed devices (ledctl --listed-only). +* Documentation improvements. + +Bug fixes + +* Detecting nvme disks during scan. +* Keep failure state after VMD reconnecting. +* Blinking failure LED after removing disk from RAID. +* Refactoring of SES-2 protocol implementation. SES minor fixes. +* Logfile and log levels small improvements. + + +### v0.80 / 2016-10-28 + +[Commit list](https://github.com/intel/ledmon/compare/v0.70...v0.80) + +Enhancements + +* Support for NVMe SSD devices. +* Support for NVMe devices under VMD domains. +* Using SMP GPIO_REG_TYPE_TX register for SGPIO. +* Sending LED commands optimization. +* Documentation improvements. + +Bug fixes + +* Fix support for the Dell PCIe SSD devices. +* Handling enclosure device name change. +* Fixes around IBPI_PATTERN_LOCATE_OFF state. + + +### v0.70 / 2012-12-12 + +[Commit list](https://github.com/intel/ledmon/compare/v0.40...v0.70) + +Enhancements + +* Introduce SES-2 protocol support. + +Bug fixes + +* Minor fixes. +* Memory leaks. + + +### v0.40 / 2012-07-12 + +[Commit list](https://github.com/intel/ledmon/compare/v0.3...v0.40) + +Enhancements + +* Support for Dell backplane bays. +* Turn off all unset LEDs in ledctl. + +Bug fixes + +* IPBI pattern interpretation. + + +### v0.3 / 2012-03-06 + +[Commit list](https://github.com/intel/ledmon/compare/v0.2...v0.3) + +Enhancements + +* Support for disk drivers directly attached to SCU HBA. + +Removals + +* Remove dependency of smp_utils. + + +### v0.2 / 2011-08-24 + +[Commit list](https://github.com/intel/ledmon/compare/af8f20626e4e36cdf4bb9955fc65f22fec155580...v0.2) + +Enhancements + +* Ledmon initial version. +* Visualize the state of arrays. +* Introduce daemon app "ledmon" and LED manual control app "ledctl". + diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..60549be --- /dev/null +++ b/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 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) 19yy + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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) 19yy 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. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..63bf8a3 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,25 @@ +# +# Intel(R) Enclosure LED Utilities +# Copyright (C) 2009-2019 Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope 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. +# + +if SYSTEMD_CONDITION + OPTIONAL_SUBDIR = systemd +endif + +SUBDIRS = doc src $(OPTIONAL_SUBDIR) +EXTRA_DIST = config/config.h systemd/ledmon.service.in +dist_doc_DATA = README diff --git a/README b/README new file mode 100644 index 0000000..02f2b65 --- /dev/null +++ b/README @@ -0,0 +1,46 @@ +This package contains the Enclosure LED Utilities, version 0.94 + +Copyright (C) 2009-2020 Intel Corporation. + +All files in this package can be freely distributed and used according +to the terms of the GNU General Public License, version 2. +See http://www.gnu.org/ for details. + +1. Configure package. +------------------------- + +Run "autogen.sh" to generate compiling configurations: + ./autogen.sh + ./configure + +Run ./configure with: + --enable-systemd to configure with systemd service. + --enable-testconfig to configure with config file testing tool. + +2. Compiling the package. +------------------------- + +Run "make" command to compile the package. + +Following packages are required for building and compiling: +a. systemd-devel (libudev) +b. sg3_utils-devel +c. libpci (pciutils-devel) + +3. (Un)installing the package. +-------------------------- + +Run following commands to install package: + make install + +Run following commands to uninstall package: + make uninstall + +4. Release notes. +-------------------------- + +a. Enclosure LED Utilities is meant as a part of RHEL and SLES linux + distributions. + +b. For backplane enclosures attached to ISCI controller support is limited to + Intel(R) Intelligent Backplane. diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..c5a7472 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +autoreconf --install diff --git a/config/config.h b/config/config.h new file mode 100644 index 0000000..4b417ed --- /dev/null +++ b/config/config.h @@ -0,0 +1,64 @@ +/* + * Intel(R) Enclosure LED Utilities + * Copyright (C) 2009-2017 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#ifndef _CONFIG_H_INCLUDED_ +#define _CONFIG_H_INCLUDED_ + +#include + +/** + */ +#define _HAVE_DMALLOC_H 0 + +/** + * @brief Sets errno variable and returns. + * + * This macro sets the errno variable and makes function return -1. + * + * @param[in] __val integer value to be set. + * + * @return The macro does not return a value. + */ +#define __set_errno_and_return(__val) { errno = (__val); return -1; } + +/** + */ +#define PATH_DELIM '/' + +/** + */ +#define PATH_DELIM_STR "/" + +/** + */ +#define PATH_SEP ':' + +/** + */ +#define PATH_SEP_STR ":" + +/** + */ +#define END_LINE ('\n') + +/** + */ +#define END_LINE_STR "\n" + +#endif /* _CONFIG_H_INCLUDED_ */ diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..12014f0 --- /dev/null +++ b/configure.ac @@ -0,0 +1,89 @@ + +AC_PREREQ([2.69]) +AC_INIT([ledmon], [0.94]) +AC_CONFIG_MACRO_DIR([m4]) +AC_SUBST([PACKAGE_DATE], "February 2020") +AM_INIT_AUTOMAKE([-Wall -Werror foreign]) + +# Checks for programs. +AM_PROG_CC_C_O +AC_PROG_CC_C99 +AC_PROG_INSTALL +AC_CONFIG_HEADERS([config_ac.h]) + +AM_CFLAGS='-Wall -I../config' +AM_CPPFLAGS='-D_DEBUG -D_GNU_SOURCE -D_DEFAULT_SOURCE -DDMALLOC_DISABLE -DBUILD_LABEL=\""$(BUILD_LABEL)"\"' + + +AC_DEFUN([AX_AM_CFLAGS_ADD],[AX_CHECK_COMPILE_FLAG($1, AM_CFLAGS="$AM_CFLAGS $1")]) +AX_AM_CFLAGS_ADD([-Wformat -Werror=format-security]) +AX_AM_CFLAGS_ADD([-Werror=format-overflow=2]) +AX_AM_CFLAGS_ADD([-Werror=format-truncation=1]) +AX_AM_CFLAGS_ADD([-Werror=shift-negative-value]) +AX_AM_CFLAGS_ADD([-Werror=alloca]) +AX_AM_CFLAGS_ADD([-Werror=missing-field-initializers]) +AX_AM_CFLAGS_ADD([-Werror=format-signedness]) + +AC_SUBST([AM_CFLAGS]) +AC_SUBST([AM_CPPFLAGS]) + +# Automake 1.11 - silent build rules +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) + +PKG_PROG_PKG_CONFIG + +# Checks for libraries. +AC_CHECK_LIB([rt], [shm_unlink], [], [AC_MSG_ERROR(librt not found)]) +AC_CHECK_LIB([sgutils2], [sg_ll_send_diag], [], [AC_MSG_ERROR(libsgutils not found)]) +PKG_CHECK_MODULES([LIBUDEV], [libudev]) +PKG_CHECK_MODULES([LIBPCI], [libpci]) + +# Checks for header files. +AC_CHECK_HEADERS([fcntl.h inttypes.h limits.h stdint.h stdlib.h string.h sys/file.h sys/ioctl.h sys/param.h sys/time.h syslog.h unistd.h]) + +# Checks for typedefs, structures, and compiler characteristics. +AC_CHECK_HEADER_STDBOOL +AC_C_INLINE +AC_TYPE_PID_T +AC_TYPE_SIZE_T +AC_CHECK_MEMBERS([struct stat.st_blksize]) +AC_CHECK_MEMBERS([struct stat.st_rdev]) +AC_TYPE_UINT16_T +AC_TYPE_UINT32_T +AC_TYPE_UINT64_T +AC_TYPE_UINT8_T + +# Checks for library functions. +AC_FUNC_FORK +AC_FUNC_MALLOC +AC_FUNC_MMAP +AC_CHECK_FUNCS([ftruncate memset munmap realpath regcomp select strcasecmp strchr strdup strerror strrchr strstr strtol strtoul]) + +# configure options +AC_ARG_ENABLE(systemd, AS_HELP_STRING([--enable-systemd], [install ledmon systemd service])) +AC_ARG_ENABLE(testconfig, AS_HELP_STRING([--enable-testconfig], [build test_config tool])) + +AS_IF([test "x$enable_systemd" = xyes], [SYSTEMD_STR=yes], [SYSTEMD_STR=no]) +AS_IF([test "x$enable_testconfig" = xyes], [TESTCONFIG_STR=yes], [TESTCONFIG_STR=no]) + +AM_CONDITIONAL([SYSTEMD_CONDITION], [test "$SYSTEMD_STR" = yes]) +AM_CONDITIONAL([TESTCONFIG_CONDITION], [test "$TESTCONFIG_STR" = yes]) + +# target directory for ledmon service file +AC_SUBST([SYSTEMD_PATH], "$(pkg-config systemd --variable=systemdsystemunitdir)") + +AC_CONFIG_FILES([Makefile + doc/Makefile + src/Makefile + systemd/Makefile]) +AC_OUTPUT +AC_MSG_RESULT([ +$PACKAGE_NAME $VERSION configuration: + + Source code location: ${srcdir} + Preprocessor flags: ${AM_CPPFLAGS} ${CPPFLAGS} + C compiler flags: ${AM_CFLAGS} ${CFLAGS} + Common install location: ${prefix} + configure parameters: --enable-systemd=${SYSTEMD_STR} + --enable-testconfig=${TESTCONFIG_STR} +]) diff --git a/doc/Makefile.am b/doc/Makefile.am new file mode 100644 index 0000000..0257062 --- /dev/null +++ b/doc/Makefile.am @@ -0,0 +1,35 @@ +# +# Intel(R) Enclosure LED Utilities +# Copyright (C) 2009-2019 Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope 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. +# + +CLEANFILES = ledmon.conf.5.gz ledmon.8.gz ledctl.8.gz +EXTRA_DIST = ledmon.conf.pod ledmon.pod ledctl.pod + +dist_man5_MANS = ledmon.conf.5.gz +dist_man8_MANS = ledmon.8.gz ledctl.8.gz + +ledmon.conf.5.gz: ledmon.conf.pod + pod2man -r "LEDMON.CONF Version $(PACKAGE_VERSION) $(BUILD_LABEL)" -d "@PACKAGE_DATE@" \ + -s 5 -n ledmon.conf -c "Intel(R) Enclosure LED Utilities Config" $< | gzip -9f > $@ + +ledmon.8.gz: ledmon.pod + pod2man -r "LEDMON Version $(PACKAGE_VERSION) $(BUILD_LABEL)" -d "@PACKAGE_DATE@" \ + -s 8 -n ledmon -c "Intel(R) Enclosure LED Monitor Service" $< | gzip -9f > $@ + +ledctl.8.gz: ledctl.pod + pod2man -r "LEDCTL Version $(PACKAGE_VERSION) $(BUILD_LABEL)" -d "@PACKAGE_DATE@" \ + -s 8 -n ledctl -c "Intel(R) Enclosure LED Control Application" $< | gzip -9f > $@ diff --git a/doc/ledctl.pod b/doc/ledctl.pod new file mode 100644 index 0000000..61cfc4b --- /dev/null +++ b/doc/ledctl.pod @@ -0,0 +1,330 @@ +# +# Intel(R) Enclosure LED Utilities +# Copyright (C) 2009-2020 Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope 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. +# +=head1 NAME + +ledctl - Intel(R) LED control application for a storage enclosures. + +=head1 SYNOPSIS + +B [I] I=I ... + +=head1 DESCRIPTION + +The ledctl is an user space application designed to control LEDs associated with each +slot in an enclosure or a drive bay. The LEDs of devices listed in I +are set to the given pattern I and all other LEDs are turned off. +User must have root privileges to use this application. + +There are two types of systems: 2-LEDs systems (Activity LED, Status LED) +and 3-LEDs systems (Activity LED, Locate LED, Fail LED). +The ledctl application uses SGPIO and SES-2 protocol to control LEDs. +The program implements IBPI patterns of SFF-8489 specification for SGPIO. +Please note some enclosures do not stick close to SFF-8489 specification. +It might happen that enclosure's processor will accept an IBPI pattern but it will +blink the LEDs at variance with SFF-8489 specification or it has limited number +of patterns supported. + +LED management (AHCI) and S protocols are not supported. + +The ledctl application has been verified to work with Intel(R) storage +controllers (i.e. Intel(R) AHCI controller and Intel(R) SAS controller). +The application might work with storage controllers of other vendors +(especially SCSI/SAS controllers). However, storage controllers of other +vendors have not been tested. + +The ledmon application has the highest priority when accessing LEDs. +It means that some patterns set by ledctl may have no effect if ledmon is running +(except Locate pattern). + +The ledctl application is a part of Intel(R) Enclosure LED Utilities. + +=head2 Pattern Names + +The ledctl application accepts the following names for I argument +according to SFF-8489 specification. + +=over 8 + +=item B + +Turns Locate LED associated with the given device(s) on. + +=item B + +Turns only Locate LED off. + +=item B + +Turns Status LED, Failure LED and Locate LED off. + +=item B + +Turns only Status LED and Failure LED off. + +=item B or B + +Visualizes "In a Critical Array" pattern. + +=item B + +Visualizes "Rebuild" pattern. + +=item B or B + +Visualizes "In a Failed Array" pattern. + +=item B + +Visualizes "Hotspare" pattern. + +=item B + +Visualizes "Predicted Failure Analysis" pattern. + +=item B or B + +Visualizes "Failure" pattern. + +=item B + +SES-2 R/R ABORD + +=item B + +SES-2 REBUILD/REMAP + +=item B + +SES-2 IN FAILED ARRAY + +=item B + +SES-2 IN CRIT ARRAY + +=item B + +SES-2 CONS CHECK + +=item B + +SES-2 HOT SPARE + +=item B + +SES-2 RSVD DEVICE + +=item B + +SES-2 OK + +=item B + +SES-2 IDENT + +=item B + +SES-2 REMOVE + +=item B + +SES-2 INSERT + +=item B + +SES-2 MISSING + +=item B + +SES-2 DO NOT REMOVE + +=item B + +SES-2 ACTIVE + +=item B + +SES-2 ENABLE BYP B + +=item B + +SES-2 ENABLE BYP A + +=item B + +SES-2 DEVICE OFF + +=item B + +SES-2 FAULT + +=item B + +SES-2 PRDFAIL + +=back + +=head2 Patterns Translation + +When non SES-2 pattern is send to device in enclosure automatic +translation is being done. + +=over 8 + +=item B + +I is translated to I + +=item B + +I is translated to I<~ses_ident> + +=item B or B + +I or I is translated to I + +=item B or B + +I or I is translated to I + +=item B + +I is translated to I + +=item B or B + +I or I is translated to I + +=item B + +I is translated to I + +=item B + +I is translated to I + +=item B or B + +I or I is translated to I + +=back + +=head2 List of Devices + +The application accepts a list of devices in two formats. The first +format is a list with comma separated elements. The second format is S in curly braces and elements are separated by space. See examples +section below for details. + +A device is a path to file in /dev directory or in /sys/block directory. +It may identify a block device, a RAID device or a container device. +In case of a RAID device or a container device a state will be set for all +block devices associated, respectively. + +The LEDs of devices listed in I are set to the given +pattern I and all other LEDs are turned off (unless --listed-only +option is given). + +=head1 OPTIONS + +=over 8 + +=item B<-l> or B<--log>=I + +Sets a path to local log file. If this option is specified the global log +file F is not used. + +=item B<-h> or B<--help> + +Prints this text out and exits. + +=item B<-v> or B<--version> + +Displays version of ledctl and information about the license and exits. + +=item B<-L> or B<--list-controllers> + +Prints information (system path and type) of all controllers detected by +ledmon and exits. + +=item B<-x> or B<--listed-only> + +With this option ledctl will change state only on devices listed in CLI. The +rest of devices will not be touched. + +=item B<--quiet> or B<--error> or B<--warning> or B<--info> or B<--debug> or B<--all> + +Verbose level - 'quiet' means no logging at all and 'all' means to log +everything. The levels are given in order. If user specifies more then one +verbose option the last option comes into effect. The default level is +'warning'. Verbose level also can be set by B<--log-level>=I. + +=back + +=head1 FILES + +=over 8 + +=item F + +Global log file, used by all instances of ledctl application. To force logging +to user defined file use I<-l> option switch. + +=back + +=head1 EXAMPLES + +The following example illustrates how to locate a single block device. + + ledctl locate=/dev/sda + +The following example illustrates how to turn Locate LED off for the same block device. + + ledctl locate_off=/dev/sda + +The following example illustrates how to locate disks of a RAID device and +how to set rebuild pattern for two block devices at the same time. This example +uses both formats of device list. + + ledctl locate=/dev/md127 rebuild={ /sys/block/sd[a-b] } + +The following example illustrates how to turn Status LED and Failure LED off for +the given device(s). + + ledctl off={ /dev/sda /dev/sdb } + +The following example illustrates how to locate a three block devices. This +example uses the first format of device list. + + ledctl locate=/dev/sda,/dev/sdb,/dev/sdc + +=head1 LICENSE + +Copyright (c) 2009-2017 Intel Corporation. + +This program is distributed under the terms of the GNU General Public License +as published by the Free Software Foundation. See the built-in help for +details on the License and the lack of warranty. + +=head1 SEE ALSO + +ledmon(8), ledmon.conf(5) + +=head1 AUTHOR + +This manual page was written by Artur Wojcik . It may +be used by others. diff --git a/doc/ledmon.conf.pod b/doc/ledmon.conf.pod new file mode 100644 index 0000000..478bee0 --- /dev/null +++ b/doc/ledmon.conf.pod @@ -0,0 +1,124 @@ +# +# Intel(R) Enclosure LED Utilities +# Copyright (C) 2009-2017 Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope 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. +# +=head1 NAME + +ledmon.conf - Configuration file for Intel(R) Enclosure LED Utilities. + +=head1 DESCRIPTION + +The ledmon configuration file allows you to use advanced settings and functions +of Intel(R) Enclosure LED Utilities. The global location of the configuration +file is F. Instead of a global configuration file, you can +specify a local config file using the I<-c> option when running ledmon. + +=head2 SYNTAX + +One line should keep exactly one option and value in the configuration file in +format: OPTION=VALUE. Any word that begins with a hash sign (B<#>) starts a +comment and that word together with the remainder of the line is ignored. Empty +lines are allowed. Either single quotes (B<'>) or double quotes (B<">) should +not be used. + +Values are considered as truth: enabled, true, yes, 1. + +Values are considered as false: disabled, false, no, 0. + +See also the examples section. + +=head2 List of configurable options: + +B - Ledmon will exclude scanning controllers listed on blacklist. +When whitelist is also set in config file, the blacklist will be ignored. +The controllers should be separated by comma (B<,>) character. + +B - Related with RAID Initialization (resync), Verify (check) +and Verify and Fix (repair) processes. If value is set to true - status LEDs of +all member drives will blink with proper pattern if RAID volume is under sync +process. If value is set to false, processes like init or verifications will not +be reported by LEDs. The default value is true. + +B - RAID can be migrated between some levels or strip sizes and +the flag is related with this processes. Also RAID Grow operation will be +reported along with this flag. If value is set to true - status LEDs of all +member drives will blink with proper pattern if RAID volume is under reshape. +If value is set to false, listed actions will not be reported by LEDs. The +default value is true. + +B - The value is given in seconds. Defines time interval between +ledmon sysfs scan. The minimum is 5 seconds the maximum is not specified. The +default value is 10 seconds. + +B - Corresponds with I<--log-level> flag from ledmon. Log level QUIET +means no logging at all and ALL means to log everything. The default log level +is WARNING. Acceptable values are: quiet, error, warning, info, debug, all. +Value also can be set by integer number - 0 means 'quiet' and 5 means 'all'. + +B - Sets a path to local log file. If this option is specified the +global log file F is not used. + +B - If flag is set to true ledmon will limit monitoring only +to drives that are RAID members. The default value is false. + +B - Flag is related with RAID rebuild process. When value +is set to false - only the drive that the RAID is rebuilding to will be marked +with appropriate LED pattern. If value is set to true all drives from RAID +that is during rebuild will blink during this operation. + +B - Ledmon will limit changing LED state to controllers listed on +whitelist. If any whitelist is set, only devices from list will be scanned by +ledmon. The controllers should be separated by comma (B<,>) character. + +=head1 EXAMPLES + +=head2 Excluding one controller from ledmon scans, changing log level and scans +interval: + +LOG_LEVEL=all + +INTERVAL=5 + +#Exclude disks from SATA controller + +BLACKLIST=/sys/devices/pci0000:00/0000:00:17.0 + +=head2 Blink only on RAID members, blink on all disks during rebuild and ignore +init phase: + +RAID_MEMBERS_ONLY=true + +BLINK_ON_INIT=false + +REBUILD_BLINK_ON_ALL=true + + +=head1 LICENSE + +Copyright (c) 2009-2017 Intel Corporation. + +This program is distributed under the terms of the GNU General Public License +as published by the Free Software Foundation. See the built-in help for details +on the License and the lack of warranty. + +=head1 SEE ALSO + +ledmon(8), ledctl(8) + +=head1 AUTHOR + +This manual page was written by Michal Zylowski . It +may be used by others. diff --git a/doc/ledmon.pod b/doc/ledmon.pod new file mode 100644 index 0000000..17f27b3 --- /dev/null +++ b/doc/ledmon.pod @@ -0,0 +1,137 @@ +# +# Intel(R) Enclosure LED Utilities +# Copyright (C) 2009-2019 Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope 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. +# +=head1 NAME + +ledmon - Intel(R) LED monitor service for storage enclosures. + +=head1 SYNOPSIS + +B [I] + +=head1 DESCRIPTION + +The ledmon application is a daemon process used to monitor a state of +software RAID devices (md only) or a state of block devices. The state +is visualizing on LEDs associated to each slot in an enclosure or a +drive bay. There are two types of system: 2-LEDs system (Activity LED, +Status LED) and 3-LEDs system (Activity LED, Locate LED, Fail +LED). This application has the highest priority when accessing the +LEDs. + +The ledmon application uses SGPIO and SES-2 protocol to control +LEDs. The program implements IBPI patterns of SFF-8489 specification +for SGPIO. Please note some enclosures do not stick close to SFF-8489 +specification. It might happen that enclosure processor will accept +IBPI pattern but it will blink LEDs not according to SFF-8489 +specification or it has limited number of patterns supported. + +LED management (AHCI) and SAF-TE protocols are not supported. + +There's no method provided to specify which RAID volume should be monitored +and which not. The ledmon application monitors all RAID devices and visualizes +their state. + +The ledmon application has been verified to work with Intel(R) storage +controllers (i.e. Intel(R) AHCI controller and Intel(R) SAS controller). +The application might work with storage controllers of other vendors +(especially SAS/SCSI controllers). However storage controllers of other +vendors have not been tested. + +The ledmon application is part of Intel(R) Enclosure LED Utilities. Only +single instance of the application is allowed. + +=head1 OPTIONS + +=over 8 + +=item B<-c> or B<--config>=I + +Sets a path to local configuration file. If this option is specified the +global configuration file and user configuration file has no effect. + +=item B<-l> or B<--log>=I + +Sets a path to local log file. If this option is specified the global log +file F is not used. + +=item B<-t> or B<--interval>=I + +Sets time interval between scans of sysfs. The value is given in seconds. +The minimum is 5 seconds the maximum is not specified. + +=item B<--quiet> or B<--error> or B<--warning> or B<--info> or B<--debug> or B<--all> + +Verbose level - 'quiet' means no logging at all and 'all' means to log +everything. The levels are given in order. If user specifies more then one +verbose option the last option comes into effect. The default level is +'warning'. Verbose level also can be set by B<--log-level>=I. + +=item B<--foreground> + +Run process foreground instead of a daemon. This option is useful in +systemd service file. Another use case of this option is debugging with +elevated B<--log-level>=I. + +=item B<-h> or B<--help> + +Prints this text out and exits. + +=item B<-v> or B<--version> + +Displays version of ledmon and information about the license and exits. + +=back + +=head1 FILES + +=over 8 + +=item F + +Global log file, used by ledmon application. To force logging to user defined +file use I<-l> option switch. + +=item F + +Global configuration file, shared between ledmon and all ledctl application +instances. Local configuration file can be used by running ledmon with I<-c> +switch. + +=back + +=head1 LICENSE + +Copyright (c) 2009-2017 Intel Corporation. + +This program is distributed under the terms of the GNU General Public License +as published by the Free Software Foundation. See the build-in help for details +on the License and the lack of warranty. + +=head1 BUGS + +The ledmon application does not recognize PFA state (Predicted Failure Analysis), +hence the PFA pattern from SFF-8489 specification is not visualized. + +=head1 SEE ALSO + +ledctl(8), ledmon.conf(5) + +=head1 AUTHOR + +This manual page was written by Artur Wojcik . It may +be used by others. diff --git a/m4/m4_ax_check_compile_flag.m4 b/m4/m4_ax_check_compile_flag.m4 new file mode 100644 index 0000000..bd753b3 --- /dev/null +++ b/m4/m4_ax_check_compile_flag.m4 @@ -0,0 +1,53 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT]) +# +# DESCRIPTION +# +# Check whether the given FLAG works with the current language's compiler +# or gives an error. (Warnings, however, are ignored) +# +# ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on +# success/failure. +# +# If EXTRA-FLAGS is defined, it is added to the current language's default +# flags (e.g. CFLAGS) when the check is done. The check is thus made with +# the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to +# force the compiler to issue an error when a bad flag is given. +# +# INPUT gives an alternative input source to AC_COMPILE_IFELSE. +# +# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this +# macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG. +# +# LICENSE +# +# Copyright (c) 2008 Guido U. Draheim +# Copyright (c) 2011 Maarten Bosmans +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 6 + +AC_DEFUN([AX_CHECK_COMPILE_FLAG], +[AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF +AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl +AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [ + ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS + _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1" + AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])], + [AS_VAR_SET(CACHEVAR,[yes])], + [AS_VAR_SET(CACHEVAR,[no])]) + _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags]) +AS_VAR_IF(CACHEVAR,yes, + [m4_default([$2], :)], + [m4_default([$3], :)]) +AS_VAR_POPDEF([CACHEVAR])dnl +])dnl AX_CHECK_COMPILE_FLAGS diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..c6b7ea0 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,49 @@ +# +# Intel(R) Enclosure LED Utilities +# Copyright (C) 2009-2020 Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope 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. +# + +COMMON_SRCS = ahci.c block.c cntrl.c config_file.c enclosure.c list.c \ + raid.c scsi.c slave.c status.c sysfs.c smp.c dellssd.c \ + utils.c pci_slot.c vmdssd.c udev.c amd.c amd_sgpio.c amd_ipmi.c\ + ipmi.c npem.c\ + ahci.h amd_sgpio.h block.h cntrl.h config_file.h dellssd.h \ + enclosure.h ibpi.h list.h pci_slot.h pidfile.h raid.h scsi.h \ + ses.h slave.h smp.h status.h sysfs.h udev.h utils.h version.h \ + vmdssd.h ipmi.h amd.h amd_ipmi.h npem.h + +LEDMON_SRCS = ledmon.c pidfile.c $(COMMON_SRCS) +LEDCTL_SRCS = ledctl.c $(COMMON_SRCS) +TEST_CONFIG_SRCS = config_file.c list.c utils.c + + +sbin_PROGRAMS = ledmon ledctl +ledmon_SOURCES = $(LEDMON_SRCS) +ledctl_SOURCES = $(LEDCTL_SRCS) + +ledmon_LDADD = $(LIBPCI_LIBS) $(LIBUDEV_LIBS) +ledctl_LDADD = $(LIBPCI_LIBS) $(LIBUDEV_LIBS) + +ledmon_CFLAGS = $(AM_CFLAGS) $(LIBPCI_CFLAGS) $(LIBUDEV_CFLAGS) +ledctl_CFLAGS = $(AM_CFLAGS) $(LIBPCI_CFLAGS) $(LIBUDEV_CFLAGS) +if TESTCONFIG_CONDITION + +noinst_PROGRAMS = test_config +test_config_SOURCES = $(TEST_CONFIG_SRCS) +test_config_CPPFLAGS = $(AM_CPPFLAGS) -D_TEST_CONFIG + +endif + diff --git a/src/ahci.c b/src/ahci.c new file mode 100644 index 0000000..49a9ace --- /dev/null +++ b/src/ahci.c @@ -0,0 +1,129 @@ +/* + * Intel(R) Enclosure LED Utilities + * Copyright (C) 2009-2019 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#if _HAVE_DMALLOC_H +#include +#endif + +#include "ahci.h" +#include "config.h" +#include "utils.h" + +/** + * Time interval in nano seconds to wait before enclosure management message + * is being sent to AHCI controller. + */ +#define EM_MSG_WAIT 1500000 /* 0.0015 seconds */ + +/** + * This array maps IBPI pattern to value recognized by AHCI driver. The driver + * uses this control number to issue SGPIO signals appropriately. + */ +static const unsigned int ibpi2sgpio[] = { + [IBPI_PATTERN_REBUILD] = 0x00480000, + [IBPI_PATTERN_FAILED_DRIVE] = 0x00400000, + [IBPI_PATTERN_LOCATE] = 0x00080000, + [IBPI_PATTERN_UNKNOWN] = 0x00000000, + [IBPI_PATTERN_ONESHOT_NORMAL] = 0x00000000, + [IBPI_PATTERN_NORMAL] = 0x00000000, + [IBPI_PATTERN_LOCATE_OFF] = 0x00000000, +#ifdef DEBUG_IBPI + [IBPI_PATTERN_DEGRADED] = 0x00200000, + [IBPI_PATTERN_FAILED_ARRAY] = 0x00280000, + [IBPI_PATTERN_HOTSPARE] = 0x01800000, + [IBPI_PATTERN_PFA] = 0x01400000 +#else + [IBPI_PATTERN_DEGRADED] = 0x00000000, + [IBPI_PATTERN_FAILED_ARRAY] = 0x00000000, + [IBPI_PATTERN_HOTSPARE] = 0x00000000, + [IBPI_PATTERN_PFA] = 0x00000000 +#endif +}; + +/* + * The function sends a LED control message to AHCI controller. It uses + * SGPIO to control the LEDs. See ahci.h for details. + */ +int ahci_sgpio_write(struct block_device *device, enum ibpi_pattern ibpi) +{ + char temp[WRITE_BUFFER_SIZE]; + char path[PATH_MAX]; + char *sysfs_path = device->cntrl_path; + const struct timespec waittime = { + .tv_sec = 0, + .tv_nsec = EM_MSG_WAIT + }; + + /* write only if state has changed */ + if (ibpi == device->ibpi_prev) + return 1; + + if (sysfs_path == NULL) + __set_errno_and_return(EINVAL); + if ((ibpi < IBPI_PATTERN_NORMAL) || (ibpi > IBPI_PATTERN_LOCATE_OFF)) + __set_errno_and_return(ERANGE); + + sprintf(temp, "%u", ibpi2sgpio[ibpi]); + + snprintf(path, sizeof(path), "%s/em_message", sysfs_path); + + nanosleep(&waittime, NULL); + return buf_write(path, temp) > 0; +} + +#define SCSI_HOST "/scsi_host" +/* + * The function return path to SATA port in sysfs tree. See ahci.h for details. + */ + +char *ahci_get_port_path(const char *path) +{ + char *p; + char tmp[PATH_MAX]; + char *buf; + size_t buf_size; + + p = strstr(path, "/target"); + if (p == NULL) + return NULL; + + if (sizeof(tmp) <= (p - path)) + return NULL; + strncpy(tmp, path, p - path); + tmp[p - path] = '\0'; + p = strrchr(tmp, PATH_DELIM); + if (p == NULL) + return NULL; + + buf_size = strlen(tmp) + strlen(p) + strlen(SCSI_HOST) + 1; + buf = malloc(buf_size); + if (buf == NULL) + return NULL; + + snprintf(buf, buf_size, "%s%s%s", tmp, SCSI_HOST, p); + return buf; +} diff --git a/src/ahci.h b/src/ahci.h new file mode 100644 index 0000000..7cd05e0 --- /dev/null +++ b/src/ahci.h @@ -0,0 +1,54 @@ +/* + * Intel(R) Enclosure LED Utilities + * Copyright (C) 2009-2017 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#ifndef _AHCI_H_INCLUDED_ +#define _AHCI_H_INCLUDED_ + +#include "block.h" +#include "ibpi.h" + +/** + * @brief Gets sysfs path to SATA port. + * + * The function returns a path to SATA port in sysfs tree the given block device + * is connected to. + * + * @param[in] path Path to block device in sysfs tree. + * + * @return Canonical path if successful, otherwise NULL pointer if an error occurred. + */ +char *ahci_get_port_path(const char *path); + +/** + * @brief Sends LED control message using SGPIO. + * + * This function visualizes IBPI pattern on LEDs associated with a slot in + * drive bay. This function is designed to send messaged to AHCI controller + * only. + * + * @param[in] path Path in sysfs tree to slot in drive bay. + * @param[in] ibpi IBPI pattern to visualize on LEDs associated + * with the given slot. + * + * @return Number of bytes send to controller, -1 means error occurred and + * errno has additional error information. + */ +int ahci_sgpio_write(struct block_device *path, enum ibpi_pattern ibpi); + +#endif /* _AHCI_H_INCLUDED_ */ diff --git a/src/amd.c b/src/amd.c new file mode 100644 index 0000000..d66c917 --- /dev/null +++ b/src/amd.c @@ -0,0 +1,190 @@ +/* + * AMD LED control + * Copyright (C) 2019, Advanced Micro Devices, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if _HAVE_DMALLOC_H +#include +#endif + +#include "config.h" +#include "ibpi.h" +#include "list.h" +#include "utils.h" +#include "amd.h" +#include "amd_sgpio.h" +#include "amd_ipmi.h" + +enum amd_led_interfaces amd_interface = AMD_INTF_UNSET; +enum amd_platforms amd_platform = AMD_PLATFORM_UNSET; + +int _find_file_path(const char *start_path, const char *filename, + char *path, size_t path_len) +{ + int rc, found; + struct stat sbuf; + struct list dir; + char *dir_name; + const char *dir_path; + + rc = scan_dir(start_path, &dir); + if (rc) { + log_info("Failed to scan %s", start_path); + return 0; + } + + found = 0; + list_for_each(&dir, dir_path) { + dir_name = strrchr(dir_path, '/'); + if (!dir_name) + continue; + + /* skip past the leading '/' */ + dir_name++; + + if (strncmp(dir_name, filename, strlen(filename)) == 0) { + char tmp[PATH_MAX + 1]; + + strncpy(tmp, dir_path, path_len); + snprintf(path, path_len, "%s", dirname(tmp)); + + found = 1; + break; + } + + if (lstat(dir_path, &sbuf) == -1) + continue; + + if (S_ISDIR(sbuf.st_mode)) { + found = _find_file_path(dir_path, filename, + path, path_len); + if (found) + break; + } + } + + list_erase(&dir); + return found; +} + +static void _get_amd_led_interface(void) +{ + char *name; + + name = get_text("/sys/class/dmi/id", "product_name"); + if (!name) + return; + + if (!strncmp(name, "ETHANOL_X", 9)) { + amd_interface = AMD_INTF_IPMI; + amd_platform = AMD_PLATFORM_ETHANOL_X; + } else if (!strncmp(name, "DAYTONA_X", 9)) { + amd_interface = AMD_INTF_IPMI; + amd_platform = AMD_PLATFORM_DAYTONA_X; + } else if (!strncmp(name, "GRANDSTAND", 10)) { + amd_interface = AMD_INTF_SGPIO; + amd_platform = AMD_PLATFORM_GRANDSTAND; + } else if (!strncmp(name, "Speedway", 8)) { + amd_interface = AMD_INTF_SGPIO; + amd_platform = AMD_PLATFORM_SPEEDWAY; + } + + free(name); +} + +int amd_em_enabled(const char *path) +{ + int rc; + + _get_amd_led_interface(); + + switch (amd_interface) { + case AMD_INTF_SGPIO: + rc = _amd_sgpio_em_enabled(path); + break; + case AMD_INTF_IPMI: + rc = _amd_ipmi_em_enabled(path); + break; + default: + log_error("Unsupported AMD interface\n"); + rc = -EOPNOTSUPP; + break; + } + + return rc; +} + +int amd_write(struct block_device *device, enum ibpi_pattern ibpi) +{ + int rc; + + /* write only if state has changed */ + if (ibpi == device->ibpi_prev) + return 1; + + switch (amd_interface) { + case AMD_INTF_SGPIO: + rc = _amd_sgpio_write(device, ibpi); + break; + case AMD_INTF_IPMI: + rc = _amd_ipmi_write(device, ibpi); + break; + case AMD_INTF_UNSET: + default: + log_error("Unsupported AMD interface\n"); + rc = -EOPNOTSUPP; + break; + } + + return rc; +} + +char *amd_get_path(const char *cntrl_path, const char *sysfs_path) +{ + char *path; + + switch (amd_interface) { + case AMD_INTF_SGPIO: + path = _amd_sgpio_get_path(sysfs_path); + break; + case AMD_INTF_IPMI: + path = _amd_ipmi_get_path(cntrl_path, sysfs_path); + break; + case AMD_INTF_UNSET: + default: + log_error("Unsupported AMD interface\n"); + path = NULL; + break; + } + + return path; +} diff --git a/src/amd.h b/src/amd.h new file mode 100644 index 0000000..8bdb3a9 --- /dev/null +++ b/src/amd.h @@ -0,0 +1,61 @@ +/* + * AMD LED control + * Copyright (C) 2019, Advanced Micro Devices, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#include "block.h" + +enum amd_device_type {AMD_NO_DEVICE, AMD_SATA_DEVICE, AMD_NVME_DEVICE}; + +struct amd_drive { + int ata_port; + int port; + int drive_bay; + int initiator; + uint8_t channel; + uint8_t slave_addr; + enum amd_device_type dev; +}; + +enum amd_led_interfaces { + AMD_INTF_UNSET, + AMD_INTF_SGPIO, + AMD_INTF_IPMI, +}; + +extern enum amd_led_interfaces amd_interface; + +enum amd_platforms { + AMD_PLATFORM_UNSET, + AMD_PLATFORM_ETHANOL_X, + AMD_PLATFORM_DAYTONA_X, + AMD_PLATFORM_GRANDSTAND, + AMD_PLATFORM_SPEEDWAY, +}; + +extern enum amd_platforms amd_platform; + +int amd_em_enabled(const char *path); +int amd_write(struct block_device *device, enum ibpi_pattern ibpi); +char *amd_get_path(const char *cntrl_path, const char *sysfs_path); + +int _find_file_path(const char *start_path, const char *filename, + char *path, size_t path_len); + +/* Register dump formats used for debug output */ +#define REG_FMT_2 "%23s: %-4x%23s: %-4x\n" +#define REG_FMT_1 "%23s: %-4x\n" diff --git a/src/amd_ipmi.c b/src/amd_ipmi.c new file mode 100644 index 0000000..6a5b28c --- /dev/null +++ b/src/amd_ipmi.c @@ -0,0 +1,454 @@ +/* + * AMD IPMI LED control + * Copyright (C) 2019, Advanced Micro Devices, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if _HAVE_DMALLOC_H +#include +#endif + +#include "config.h" +#include "ibpi.h" +#include "list.h" +#include "utils.h" +#include "amd.h" +#include "ipmi.h" + +static uint8_t amd_ibpi_ipmi_register[] = { + [IBPI_PATTERN_PFA] = 0x41, + [IBPI_PATTERN_LOCATE] = 0x42, + [IBPI_PATTERN_FAILED_DRIVE] = 0x44, + [IBPI_PATTERN_FAILED_ARRAY] = 0x45, + [IBPI_PATTERN_REBUILD] = 0x46, + [IBPI_PATTERN_HOTSPARE] = 0x47, +}; + +/* The path we are given should be similar to + * /sys/devices/pci0000:e0/0000:e0:03.3/0000:e3:00.0 + * ^^^^^^^^^^ + * We need to retrieve the address from the path (indicated above) + * then use it to find the corresponding address for a slot in + * /sys/bus/pci_slots to determine the icorrect port for the NVMe device. + */ +static int _get_ipmi_nvme_port(char *path) +{ + int rc; + char *p, *f; + struct list dir; + const char *dir_path; + char *port_name; + int port = -1; + + p = strrchr(path, '/'); + if (!p) { + log_error("Couldn't parse NVMe path to determine port\n"); + return -1; + } + + p++; + + /* p now points to the address, remove the bits after the '.' */ + f = strchr(p, '.'); + if (!f) { + log_error("Couldn't parse NVMe port address\n"); + return -1; + } + + *f = '\0'; + + rc = scan_dir("/sys/bus/pci/slots", &dir); + if (rc) + return -1; + + list_for_each(&dir, dir_path) { + port_name = get_text(dir_path, "address"); + if (port_name && !strcmp(port_name, p)) { + char *dname = strrchr(dir_path, '/'); + + dname++; + port = strtol(dname, NULL, 0); + break; + } + } + + list_erase(&dir); + + /* Some platfroms require an adjustment to the port value based + * on how they are numbered by the BIOS. + */ + switch (amd_platform) { + case AMD_PLATFORM_DAYTONA_X: + port -= 2; + break; + case AMD_PLATFORM_ETHANOL_X: + port -= 7; + break; + default: + break; + } + + /* Validate port. Some BIOSes provide port values that are + * not valid. + */ + if ((port < 0) || (port > 24)) { + log_error("Invalid NVMe physical port %d\n", port); + port = -1; + } + + return port; +} + +static int _get_ipmi_sata_port(const char *start_path) +{ + int port; + char *p, *t; + char path[PATH_MAX]; + + strncpy(path, start_path, PATH_MAX); + path[PATH_MAX - 1] = 0; + t = p = strstr(path, "ata"); + + if (!p) + return -1; + + /* terminate the path after the ataXX/ part */ + p = strchr(p, '/'); + if (!p) + return -1; + *p = '\0'; + + /* skip past 'ata' to get the ata port number */ + t += 3; + port = strtoul(t, NULL, 10); + + return port; +} + +static int _get_amd_ipmi_drive(const char *start_path, + struct amd_drive *drive) +{ + int found; + char path[PATH_MAX]; + + found = _find_file_path(start_path, "nvme", path, PATH_MAX); + if (found) { + drive->port = _get_ipmi_nvme_port(path); + if (drive->port < 0) { + log_error("Could not retrieve port number\n"); + return -1; + } + + drive->drive_bay = 1 << (drive->port - 1); + drive->dev = AMD_NVME_DEVICE; + } else { + int shift; + + drive->port = _get_ipmi_sata_port(start_path); + if (drive->port < 0) { + log_error("Could not retrieve port number\n"); + return -1; + } + + /* IPMI control is handled through the MG9098 chips on + * the platform, where each MG9098 chip can control up + * to 8 drives. Since we can have multiple MG9098 chips, + * we need the drive bay relative to the set of 8 controlled + * by the MG9098 chip. + */ + shift = drive->port - 1; + if (shift >= 8) + shift %= 8; + + drive->drive_bay = 1 << shift; + drive->dev = AMD_SATA_DEVICE; + } + + log_debug("AMD Drive: port: %d, bay %x\n", drive->port, + drive->drive_bay); + + return 0; +} + +static int _ipmi_platform_channel(struct amd_drive *drive) +{ + int rc = 0; + + switch (amd_platform) { + case AMD_PLATFORM_ETHANOL_X: + drive->channel = 0xd; + break; + case AMD_PLATFORM_DAYTONA_X: + drive->channel = 0x17; + break; + default: + rc = -1; + log_error("AMD Platform does not have a defined IPMI channel\n"); + break; + } + + return rc; +} + +static int _ipmi_platform_slave_address(struct amd_drive *drive) +{ + int rc = 0; + + switch (amd_platform) { + case AMD_PLATFORM_ETHANOL_X: + drive->slave_addr = 0xc0; + break; + case AMD_PLATFORM_DAYTONA_X: + if (drive->dev == AMD_NO_DEVICE) { + /* Assume base slave address, we may not be able + * to retrieve a valid amd_drive yet. + */ + drive->slave_addr = 0xc0; + } else if (drive->dev == AMD_NVME_DEVICE) { + /* On DaytonaX systems only drive bays 19 - 24 + * support NVMe devices so use the slave address + * for the corresponding MG9098 chip. + */ + drive->slave_addr = 0xc4; + } else { + if (drive->port <= 8) + drive->slave_addr = 0xc0; + else if (drive->port > 8 && drive->port < 17) + drive->slave_addr = 0xc2; + else + drive->slave_addr = 0xc4; + } + + break; + default: + rc = -1; + log_error("AMD Platform does not have a defined IPMI slave address\n"); + break; + } + + return rc; +} + +static int _set_ipmi_register(int enable, uint8_t reg, + struct amd_drive *drive) +{ + int rc; + int status, data_sz; + uint8_t drives_status; + uint8_t new_drives_status; + uint8_t cmd_data[5]; + + memset(cmd_data, 0, sizeof(cmd_data)); + + rc = _ipmi_platform_channel(drive); + rc |= _ipmi_platform_slave_address(drive); + if (rc) + return -1; + + cmd_data[0] = drive->channel; + cmd_data[1] = drive->slave_addr; + cmd_data[2] = 0x1; + cmd_data[3] = reg; + + /* Find current register setting */ + status = 0; + + log_debug("Retrieving current register status\n"); + log_debug(REG_FMT_2, "channel", cmd_data[0], "slave addr", cmd_data[1]); + log_debug(REG_FMT_2, "len", cmd_data[2], "register", cmd_data[3]); + + rc = ipmicmd(BMC_SA, 0x0, 0x6, 0x52, 4, &cmd_data, 1, &data_sz, + &status); + if (rc) { + log_error("Could not determine current register %x setting\n", + reg); + return rc; + } + + drives_status = status; + + if (enable) + new_drives_status = drives_status | drive->drive_bay; + else + new_drives_status = drives_status & ~drive->drive_bay; + + /* Set the appropriate status */ + status = 0; + cmd_data[4] = new_drives_status; + + log_debug("Updating register status: %x -> %x\n", drives_status, + new_drives_status); + log_debug(REG_FMT_2, "channel", cmd_data[0], "slave addr", cmd_data[1]); + log_debug(REG_FMT_2, "len", cmd_data[2], "register", cmd_data[3]); + log_debug(REG_FMT_1, "status", cmd_data[4]); + + rc = ipmicmd(BMC_SA, 0x0, 0x6, 0x52, 5, &cmd_data, 1, &data_sz, + &status); + if (rc) { + log_error("Could not enable register %x\n", reg); + return rc; + } + + return 0; +} + +static int _enable_smbus_control(struct amd_drive *drive) +{ + log_debug("Enabling SMBUS Control\n"); + return _set_ipmi_register(1, 0x3c, drive); +} + +static int _enable_ibpi_state(struct amd_drive *drive, enum ibpi_pattern ibpi) +{ + log_debug("Enabling %s LED\n", ibpi2str(ibpi)); + return _set_ipmi_register(1, amd_ibpi_ipmi_register[ibpi], drive); +} + +static int _disable_ibpi_state(struct amd_drive *drive, enum ibpi_pattern ibpi) +{ + log_debug("Disabling %s LED\n", ibpi2str(ibpi)); + return _set_ipmi_register(0, amd_ibpi_ipmi_register[ibpi], drive); +} + +static int _disable_all_ibpi_states(struct amd_drive *drive) +{ + int rc; + + rc = _disable_ibpi_state(drive, IBPI_PATTERN_PFA); + rc |= _disable_ibpi_state(drive, IBPI_PATTERN_LOCATE); + rc |= _disable_ibpi_state(drive, IBPI_PATTERN_FAILED_DRIVE); + rc |= _disable_ibpi_state(drive, IBPI_PATTERN_FAILED_ARRAY); + rc |= _disable_ibpi_state(drive, IBPI_PATTERN_REBUILD); + + return rc; +} + +int _amd_ipmi_em_enabled(const char *path) +{ + int rc; + int status, data_sz; + uint8_t cmd_data[4]; + struct amd_drive drive; + + memset(&drive, 0, sizeof(struct amd_drive)); + + rc = _ipmi_platform_channel(&drive); + rc |= _ipmi_platform_slave_address(&drive); + if (rc) + return -1; + + cmd_data[0] = drive.channel; + cmd_data[1] = drive.slave_addr; + cmd_data[2] = 0x1; + cmd_data[3] = 0x63; + + status = 0; + rc = ipmicmd(BMC_SA, 0x0, 0x6, 0x52, 4, &cmd_data, 1, + &data_sz, &status); + + if (rc) { + log_error("Can't determine MG9098 Status\n"); + return 0; + } + + if (status != 98) { + log_error("Not a MG9098\n"); + return 0; + } + + return 1; +} + +int _amd_ipmi_write(struct block_device *device, enum ibpi_pattern ibpi) +{ + int rc; + struct amd_drive drive; + + log_info("\n"); + log_info("Setting %s...", ibpi2str(ibpi)); + + rc = _get_amd_ipmi_drive(device->cntrl_path, &drive); + if (rc) + return rc; + + if ((ibpi == IBPI_PATTERN_NORMAL) || + (ibpi == IBPI_PATTERN_ONESHOT_NORMAL)) { + rc = _disable_all_ibpi_states(&drive); + return rc; + } + + if (ibpi == IBPI_PATTERN_LOCATE_OFF) { + rc = _disable_ibpi_state(&drive, IBPI_PATTERN_LOCATE); + return rc; + } + + rc = _enable_smbus_control(&drive); + if (rc) + return rc; + + rc = _enable_ibpi_state(&drive, ibpi); + if (rc) + return rc; + + return 0; +} + +char *_amd_ipmi_get_path(const char *cntrl_path, const char *sysfs_path) +{ + char *p, *t; + + /* For NVMe devices we can just dup the path sysfs path */ + p = strstr(cntrl_path, "nvme"); + if (p) + return strdup(sysfs_path); + + /* For SATA devices we need everything up to 'ataXX/' in the path */ + p = strdup(cntrl_path); + if (!p) + return NULL; + + /* Find the beginning of the ataXX piece of the path */ + t = strstr(p, "ata"); + if (!t) + return NULL; + + /* Move to the '/' after the ataXX piece of the path and terminate the + * string there. + */ + t = strchr(t, '/'); + if (!t) + return NULL; + + *++t = '\0'; + + return p; +} + diff --git a/src/amd_ipmi.h b/src/amd_ipmi.h new file mode 100644 index 0000000..85a4a3f --- /dev/null +++ b/src/amd_ipmi.h @@ -0,0 +1,24 @@ +/* + * AMD IPMI LED control + * Copyright (C) 2019, Advanced Micro Devices, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#include "block.h" + +int _amd_ipmi_em_enabled(const char *path); +int _amd_ipmi_write(struct block_device *device, enum ibpi_pattern ibpi); +char *_amd_ipmi_get_path(const char *cntrl_path, const char *sysfs_path); diff --git a/src/amd_sgpio.c b/src/amd_sgpio.c new file mode 100644 index 0000000..9b1fc6c --- /dev/null +++ b/src/amd_sgpio.c @@ -0,0 +1,862 @@ +/* + * AMD SGPIO LED control + * Copyright (C) 2019, Advanced Micro Devices, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if _HAVE_DMALLOC_H +#include +#endif + +#include "config.h" +#include "ibpi.h" +#include "list.h" +#include "utils.h" +#include "amd.h" +#include "amd_sgpio.h" + +#define HOST_CAP_EMS (1 << 6) + +#define DECLARE_SGPIO(type, name, shift, mask) \ + uint32_t _##type##_##name##_shift = shift; \ + uint64_t _##type##_##name##_mask = mask << shift; \ + \ + static void set_sgpio_##type##_##name(sgpio_##type##_t * hdr, \ + sgpio_##type##_t val) \ + { \ + *hdr |= (val << _##type##_##name##_shift); \ + } \ + \ + static uint32_t get_sgpio_##type##_##name(sgpio_##type##_t * hdr)\ + { \ + return (*hdr & _##type##_##name##_mask) \ + >> _##type##_##name##_shift; \ + } + + +#define DECLARE_SGPIO_RO(type, name, shift, mask) \ + uint32_t _##type##_##name##_shift = shift; \ + uint64_t _##type##_##name##_mask = mask; \ + \ + static uint32_t get_sgpio_##type##_##name(sgpio_##type##_t * hdr)\ + { \ + return (*hdr & _##type##_##name##_mask) \ + >> _##type##_##name##_shift; \ + } + + +typedef uint32_t sgpio_hdr_t; + +#define SGPIO_HDR_MSG_TYPE_SGPIO 0x03 + +DECLARE_SGPIO(hdr, msg_type, 4, 0xF) +DECLARE_SGPIO(hdr, data_size, 8, 0xFF) +DECLARE_SGPIO(hdr, msg_size, 16, 0xFF) + +typedef uint64_t sgpio_req_t; + +#define SGPIO_REQ_REG_TYPE_CFG 0x00 +#define SGPIO_REQ_REG_TYPE_TX 0x03 +#define SGPIO_REQ_REG_TYPE_AMD 0xC0 + +DECLARE_SGPIO(req, frame_type, 0, 0xFFL) +DECLARE_SGPIO(req, function, 8, 0xFFL) +DECLARE_SGPIO(req, reg_type, 16, 0xFFL) +DECLARE_SGPIO(req, reg_index, 24, 0xFFL) +DECLARE_SGPIO(req, reg_count, 32, 0xFFL) + +typedef uint32_t sgpio_amd_t; + +DECLARE_SGPIO(amd, initiator, 0, 0x1) +DECLARE_SGPIO(amd, polarity_flip, 4, 0x1) +DECLARE_SGPIO(amd, return_to_normal, 5, 0x1) +DECLARE_SGPIO(amd, bypass_enable, 6, 0x1) + +typedef uint64_t sgpio_cfg_t; + +DECLARE_SGPIO_RO(cfg, version, 8, 0xF); +DECLARE_SGPIO_RO(cfg, gp_reg_count, 16, 0xF); +DECLARE_SGPIO_RO(cfg, cfg_reg_count, 20, 0x7); +DECLARE_SGPIO(cfg, gpio_enable, 23, 0x1); +DECLARE_SGPIO_RO(cfg, drive_count, 24, 0xFF); + +DECLARE_SGPIO(cfg, blink_gen_a, 40, 0xFL); +DECLARE_SGPIO(cfg, blink_gen_b, 44, 0xFL); +DECLARE_SGPIO(cfg, max_on, 48, 0xFL); +DECLARE_SGPIO(cfg, force_off, 52, 0xFL); +DECLARE_SGPIO(cfg, stretch_on, 56, 0xFL); +DECLARE_SGPIO(cfg, stretch_off, 60, 0xFL); + +#define DECLARE_LED(name, shift, mask) \ + uint32_t _led_##name##_shift = shift; \ + uint64_t _led_##name##_mask = mask; \ + \ + static void set_##name##_led(drive_led_t *hdr, uint32_t val) \ + { \ + *hdr |= (val << _led_##name##_shift); \ + } \ + \ + static uint32_t get_##name##_led(drive_led_t *hdr) \ + { \ + return (*hdr & _led_##name##_mask) >> _led_##name##_shift;\ + } + +typedef uint8_t drive_led_t; + +DECLARE_LED(error, 0, 0x07); +DECLARE_LED(locate, 3, 0x18); +DECLARE_LED(activity, 5, 0xE0); + +typedef struct sgpio_transmit_register { + drive_led_t drive[4]; +} __attribute__ ((__packed__)) sgpio_tx_t; + +struct amd_register { + sgpio_hdr_t hdr; + sgpio_req_t req; + sgpio_amd_t amd; +} __attribute__ ((__packed__)); + +struct config_register { + sgpio_hdr_t hdr; + sgpio_req_t req; + sgpio_cfg_t cfg; +} __attribute__ ((__packed__)); + +struct transmit_register { + sgpio_hdr_t hdr; + sgpio_req_t req; + sgpio_tx_t tx; +} __attribute__ ((__packed__)); + +static uint8_t ibpi_pattern[] = { + [IBPI_PATTERN_NONE] = 0x00, + [IBPI_PATTERN_REBUILD] = 0x07, + [IBPI_PATTERN_HOTSPARE] = 0x02, + [IBPI_PATTERN_PFA] = 0x03, + [IBPI_PATTERN_FAILED_DRIVE] = 0x00, + [IBPI_PATTERN_LOCATE] = 0x07, + [IBPI_PATTERN_LOCATE_OFF] = 0x00 +}; + +struct drive_leds { + drive_led_t error; + drive_led_t locate; + drive_led_t activity; +} __attribute__ ((__packed__)); + +#define INIT_LED(e, l, a) {.error = e, .locate = l, .activity = a} +static struct drive_leds tx_leds_blink_gen_a[] = { + [IBPI_PATTERN_NORMAL] = INIT_LED(0, 0, 0b101), + [IBPI_PATTERN_ONESHOT_NORMAL] = INIT_LED(0, 0, 0b101), + [IBPI_PATTERN_REBUILD] = INIT_LED(0b010, 0, 0), + [IBPI_PATTERN_HOTSPARE] = INIT_LED(0b010, 0, 0), + [IBPI_PATTERN_PFA] = INIT_LED(0b010, 0, 0), + [IBPI_PATTERN_FAILED_DRIVE] = INIT_LED(0b001, 0, 0), + [IBPI_PATTERN_LOCATE] = INIT_LED(0b010, 0, 0b010), + [IBPI_PATTERN_LOCATE_OFF] = INIT_LED(0, 0, 0b101), + [99] = INIT_LED(0, 0, 0b101), +}; + +static struct drive_leds tx_leds_blink_gen_b[] = { + [IBPI_PATTERN_NORMAL] = INIT_LED(0, 0, 0b101), + [IBPI_PATTERN_ONESHOT_NORMAL] = INIT_LED(0, 0, 0b101), + [IBPI_PATTERN_REBUILD] = INIT_LED(0b110, 0, 0), + [IBPI_PATTERN_HOTSPARE] = INIT_LED(0b110, 0, 0), + [IBPI_PATTERN_PFA] = INIT_LED(0b110, 0, 0), + [IBPI_PATTERN_FAILED_DRIVE] = INIT_LED(0b001, 0, 0), + [IBPI_PATTERN_LOCATE] = INIT_LED(0b110, 0, 0b110), + [IBPI_PATTERN_LOCATE_OFF] = INIT_LED(0, 0, 0b101), + [99] = INIT_LED(0, 0, 0b101), +}; + +#define CACHE_SZ 1024 +int cache_fd = 0; + +struct cache_entry { + struct drive_leds leds[4]; + uint8_t blink_gen_a; + uint8_t blink_gen_b; + uint16_t reserved; +} __attribute__ ((__packed__)); + +static struct cache_entry *sgpio_cache; + +static void _put_cache(void) +{ + munmap(sgpio_cache, CACHE_SZ); + + if (cache_fd) { + flock(cache_fd, LOCK_UN); + fsync(cache_fd); + close(cache_fd); + cache_fd = 0; + } +} + +static int _open_and_map_cache(void) +{ + struct stat sbuf; + + if (cache_fd) + return 0; + + cache_fd = shm_open("/ledmon_amd_sgpio_cache", O_RDWR | O_CREAT, + S_IRUSR | S_IWUSR); + if (cache_fd < 1) { + log_error("Couldn't open SGPIO cache: %s", strerror(errno)); + return -1; + } + + flock(cache_fd, LOCK_EX); + + fstat(cache_fd, &sbuf); + if (sbuf.st_size == 0) { + if (ftruncate(cache_fd, CACHE_SZ) != 0) { + log_error("Couldn't truncate SGPIO cache: %s", strerror(errno)); + return -1; + } + } + + sgpio_cache = mmap(NULL, CACHE_SZ, PROT_READ | PROT_WRITE, + MAP_SHARED, cache_fd, 0); + if (sgpio_cache == MAP_FAILED) { + log_error("Couldn't map SGPIO cache: %s", strerror(errno)); + _put_cache(); + return -1; + } + + return 0; +} + +static struct cache_entry *_get_cache(struct amd_drive *drive) +{ + int rc, index; + + rc = _open_and_map_cache(); + if (rc) + return NULL; + + /* The sgpio_cache is just an array of cache_entry structs, and + * each cache_entry describes the drive LED settings for 4 drives. + * To find the corresponding cache entry for an ata port we need + * to first round down to the nearest multiple of 4, then divide + * by four. This gives us the following mapping; + * cache_entry[0] => drive 0 to drive 3 + * cache_entry[1] => drive 4 to drive 7 + * cache_entry[n] => drive (4*n) to drive (4*n + 3) + */ + + index = (drive->ata_port / 4); + + return &sgpio_cache[index]; +} + +static int _send_sgpio_register(const char *em_buffer_path, void *reg, + int reg_len) +{ + int count; + int saved_errno; + int retries = 3; + + do { + int fd = open(em_buffer_path, O_WRONLY); + + if (fd < 0) { + log_error("Couldn't open EM buffer %s: %s", + em_buffer_path, strerror(errno)); + return -1; + } + + count = write(fd, reg, reg_len); + saved_errno = errno; + close(fd); + + /* Insert small sleep to ensure hardware has enough time to + * see the register change and read it. Without the sleep + * multiple writes can result in an EBUSY return because + * hardware has not cleared the EM_CTL_TM (Transmit Message) + * bit. + */ + usleep(1000); + + if (count == reg_len || saved_errno != EBUSY) + break; + } while (--retries != 0); + + if (count != reg_len) { + log_error("Couldn't write SGPIO register: %s", + strerror(saved_errno)); + return -1; + } + + return 0; +} + +static sgpio_hdr_t _init_sgpio_hdr(int data_size, int msg_size) +{ + sgpio_hdr_t hdr = 0; + + set_sgpio_hdr_msg_type(&hdr, SGPIO_HDR_MSG_TYPE_SGPIO); + set_sgpio_hdr_data_size(&hdr, data_size); + set_sgpio_hdr_msg_size(&hdr, msg_size); + + return hdr; +} + +static void _dump_sgpio_hdr(const char *type, sgpio_hdr_t hdr) +{ + log_debug("%s SGPIO Header: %08x\n", type, hdr); + log_debug(REG_FMT_2, "message type", get_sgpio_hdr_msg_type(&hdr), + "data size", get_sgpio_hdr_data_size(&hdr)); + log_debug(REG_FMT_1, "message size", get_sgpio_hdr_msg_size(&hdr)); +} + +static sgpio_req_t _init_sgpio_req(int frame_type, int function, + int reg_type, int reg_index, + int reg_count) +{ + sgpio_req_t req = 0; + + set_sgpio_req_frame_type(&req, frame_type); + set_sgpio_req_function(&req, function); + set_sgpio_req_reg_type(&req, reg_type); + set_sgpio_req_reg_index(&req, reg_index); + set_sgpio_req_reg_count(&req, reg_count); + + return req; +} + +static void _dump_sgpio_req(const char *type, sgpio_req_t req) +{ + uint32_t *r = (uint32_t *)&req; + + log_debug("%s SGPIO Request Register: %08x %08x\n", type, r[0], r[1]); + log_debug(REG_FMT_2, "frame type", get_sgpio_req_frame_type(&req), + "function", get_sgpio_req_function(&req)); + log_debug(REG_FMT_2, "register type", get_sgpio_req_reg_type(&req), + "register index", get_sgpio_req_reg_index(&req)); + log_debug(REG_FMT_1, "register count", get_sgpio_req_reg_count(&req)); +} + +static sgpio_cfg_t _init_sgpio_cfg(int gpio_enable, int blink_a, + int blink_b, int force_off, int max_on, + int stretch_off, int stretch_on) +{ + sgpio_cfg_t cfg = 0; + + if (gpio_enable) + set_sgpio_cfg_gpio_enable(&cfg, 1); + + set_sgpio_cfg_blink_gen_a(&cfg, blink_a); + set_sgpio_cfg_blink_gen_b(&cfg, blink_b); + set_sgpio_cfg_max_on(&cfg, max_on); + set_sgpio_cfg_force_off(&cfg, force_off); + set_sgpio_cfg_stretch_on(&cfg, stretch_on); + set_sgpio_cfg_stretch_off(&cfg, stretch_off); + + return cfg; +} + +static void _dump_sgpio_cfg(const char *type, sgpio_cfg_t cfg) +{ + uint32_t *r = (uint32_t *)&cfg; + + log_debug("%s SGPIO Configuration Register: %08x %08x\n", type, + r[0], r[1]); + log_debug(REG_FMT_2, "version", get_sgpio_cfg_version(&cfg), + "gp register count", get_sgpio_cfg_gp_reg_count(&cfg)); + log_debug(REG_FMT_2, "cfg register count", + get_sgpio_cfg_cfg_reg_count(&cfg), "gpio enabled", + get_sgpio_cfg_gpio_enable(&cfg)); + log_debug(REG_FMT_2, "drive count", get_sgpio_cfg_drive_count(&cfg), + "blink gen rate A", get_sgpio_cfg_blink_gen_a(&cfg)); + + log_debug(REG_FMT_2, "blink gen rate B", + get_sgpio_cfg_blink_gen_b(&cfg), "force activity off", + get_sgpio_cfg_force_off(&cfg)); + log_debug(REG_FMT_2, "max activity on", get_sgpio_cfg_max_on(&cfg), + "stretch activity off", get_sgpio_cfg_stretch_off(&cfg)); + log_debug(REG_FMT_1, "stretch activity on", + get_sgpio_cfg_stretch_on(&cfg)); +} + +static sgpio_amd_t _init_sgpio_amd(int initiator, int polarity, + int bypass, int normal) +{ + sgpio_amd_t amd = 0; + + set_sgpio_amd_initiator(&amd, initiator); + set_sgpio_amd_polarity_flip(&amd, polarity); + set_sgpio_amd_bypass_enable(&amd, bypass); + set_sgpio_amd_return_to_normal(&amd, normal); + + return amd; +} + +static void _dump_sgpio_amd(const char *type, sgpio_amd_t amd) +{ + log_debug("%s SGPIO AMD Register: %08x\n", type, amd); + log_debug(REG_FMT_2, "initiator", get_sgpio_amd_initiator(&amd), + "polarity", get_sgpio_amd_polarity_flip(&amd)); + log_debug(REG_FMT_2, "bypass enable", get_sgpio_amd_bypass_enable(&amd), + "return to normal", get_sgpio_amd_return_to_normal(&amd)); +} + +static int _write_cfg_register(const char *em_buffer_path, + struct cache_entry *cache, int ibpi) +{ + struct config_register cfg_reg; + + cfg_reg.hdr = _init_sgpio_hdr(0, sizeof(cfg_reg)); + cfg_reg.req = _init_sgpio_req(0x40, 0x82, SGPIO_REQ_REG_TYPE_CFG, + 0, 2); + + if (cache->blink_gen_a) + cache->blink_gen_b = ibpi_pattern[ibpi]; + else + cache->blink_gen_a = ibpi_pattern[ibpi]; + + cfg_reg.cfg = _init_sgpio_cfg(1, cache->blink_gen_a, + cache->blink_gen_b, 2, 1, 0, 0); + + _dump_sgpio_hdr("CFG", cfg_reg.hdr); + _dump_sgpio_req("CFG", cfg_reg.req); + _dump_sgpio_cfg("CFG", cfg_reg.cfg); + + return _send_sgpio_register(em_buffer_path, &cfg_reg, sizeof(cfg_reg)); +} + +static void _dump_sgpio_tx(const char *type, sgpio_tx_t tx) +{ + int i; + + log_debug("%s SGPIO TX Register: %08x\n", type, tx); + for (i = 0; i < 4; i++) { + log_debug("\tdrive %d: error %x, locate %x, activity %x\n", i, + get_error_led(&tx.drive[i]), + get_locate_led(&tx.drive[i]), + get_activity_led(&tx.drive[i])); + } +} + +static int _write_tx_register(const char *em_buffer_path, + struct transmit_register *tx_reg) +{ + tx_reg->hdr = _init_sgpio_hdr(0, sizeof(*tx_reg)); + tx_reg->req = _init_sgpio_req(0x40, 0x82, SGPIO_REQ_REG_TYPE_TX, + 0, 1); + + _dump_sgpio_hdr("TX", tx_reg->hdr); + _dump_sgpio_req("TX", tx_reg->req); + _dump_sgpio_tx("TX", tx_reg->tx); + + return _send_sgpio_register(em_buffer_path, tx_reg, sizeof(*tx_reg)); +} + +static void _set_tx_drive_leds(struct transmit_register *tx_reg, + struct cache_entry *cache, + int drive_bay, int ibpi) +{ + int i; + struct drive_leds *leds; + + memset(&tx_reg->tx, 0, sizeof(tx_reg->tx)); + + if (cache->blink_gen_a) + leds = &tx_leds_blink_gen_b[ibpi]; + else + leds = &tx_leds_blink_gen_a[ibpi]; + + cache->leds[drive_bay].error = leds->error; + cache->leds[drive_bay].locate = leds->locate; + cache->leds[drive_bay].activity = leds->activity; + + for (i = 0; i < 4; i++) { + set_error_led(&tx_reg->tx.drive[i], cache->leds[i].error); + set_locate_led(&tx_reg->tx.drive[i], cache->leds[i].locate); + set_activity_led(&tx_reg->tx.drive[i], cache->leds[i].activity); + } +} + +static int _init_tx_drive_leds(struct transmit_register *tx_reg, + struct cache_entry *cache) +{ + int i; + int init_done = 0; + + memset(tx_reg, 0, sizeof(*tx_reg)); + + for (i = 0; i < 4; i++) { + if (cache->leds[i].error || cache->leds[i].locate || + cache->leds[i].activity) + continue; + + _set_tx_drive_leds(tx_reg, cache, i, 99); + init_done = 1; + } + + return init_done; +} + +static int _write_amd_register(const char *em_buffer_path, + struct amd_drive *drive) +{ + struct amd_register amd_reg; + + amd_reg.hdr = _init_sgpio_hdr(0, sizeof(amd_reg)); + amd_reg.req = _init_sgpio_req(0x40, 0x82, SGPIO_REQ_REG_TYPE_AMD, + 0, 1); + amd_reg.amd = _init_sgpio_amd(drive->initiator, 0, 1, 1); + + _dump_sgpio_hdr("AMD", amd_reg.hdr); + _dump_sgpio_req("AMD", amd_reg.req); + _dump_sgpio_amd("AMD", amd_reg.amd); + + return _send_sgpio_register(em_buffer_path, &amd_reg, sizeof(amd_reg)); +} + +static int _get_amd_sgpio_drive(const char *start_path, + struct amd_drive *drive) +{ + char *a, *p; + int found; + char path[PATH_MAX]; + char ata_dir[PATH_MAX]; + + /* Start the search at the ataXX directory */ + strncpy(ata_dir, start_path, PATH_MAX); + ata_dir[PATH_MAX - 1] = 0; + a = p = strstr(ata_dir, "ata"); + if (!p) { + log_info("Couldn't find ata path for %s", start_path); + return -1; + } + + /* terminate the path after the ataXX/ part */ + p = strchr(p, '/'); + if (!p) + return 1; + *p = '\0'; + + /* skip past 'ata' to get the ata port number */ + a += 3; + drive->ata_port = strtoul(a, NULL, 10); + + found = _find_file_path(ata_dir, "port_no", path, PATH_MAX); + if (!found) { + log_info("Couldn't find 'port_no' for %s\n", ata_dir); + return -1; + } + + drive->port = get_int(path, -1, "port_no"); + + if (drive->port == -1) + return -1; + + drive->drive_bay = 8 - drive->port; + if (drive->drive_bay < 4) { + drive->initiator = 1; + } else { + drive->drive_bay -= 4; + drive->initiator = 0; + } + + log_debug("AMD Drive: port %d, ata port %d, drive bay %d, initiator %d", + drive->port, drive->ata_port, drive->drive_bay, + drive->initiator); + return 0; +} + +static int _set_ibpi(struct block_device *device, enum ibpi_pattern ibpi) +{ + int rc; + struct amd_drive drive; + struct transmit_register tx_reg; + struct cache_entry *cache; + struct cache_entry cache_dup; + + log_info("\n"); + log_info("Setting %s...", ibpi2str(ibpi)); + log_debug("\tdevice: ...%s", strstr(device->sysfs_path, "/ata")); + log_debug("\tbuffer: ...%s", strstr(device->cntrl_path, "/ata")); + + /* Retrieve the port number and correlate that to the drive slot. + * Port numbers 8..1 correspond to slot numbers 0..7. This is + * further broken down (since we can only control 4 drive slots) + * such that initiator = 1 for slots 0..3 and initiator = 0 for + * slots 4..7, the slot value is reduced by 4 for slots 4..7 so + * we can calculate the correct bits to set in the register for + * that drive. + */ + rc = _get_amd_sgpio_drive(device->sysfs_path, &drive); + if (rc) + return rc; + + cache = _get_cache(&drive); + if (!cache) + return -EINVAL; + + /* Save copy of cache entry */ + memcpy(&cache_dup, cache, sizeof(cache_dup)); + + rc = _write_amd_register(device->cntrl_path, &drive); + if (rc) + goto _set_ibpi_error; + + rc = _write_cfg_register(device->cntrl_path, cache, ibpi); + if (rc) + goto _set_ibpi_error; + + memset(&tx_reg, 0, sizeof(tx_reg)); + _set_tx_drive_leds(&tx_reg, cache, drive.drive_bay, ibpi); + rc = _write_tx_register(device->cntrl_path, &tx_reg); + +_set_ibpi_error: + if (rc) { + /* Restore saved cache entry */ + memcpy(cache, &cache_dup, sizeof(*cache)); + } + + _put_cache(); + return rc; +} + +static int _amd_sgpio_init_one(const char *path, struct amd_drive *drive, + struct cache_entry *cache) +{ + int rc, do_init; + struct transmit_register tx_reg; + + do_init = _init_tx_drive_leds(&tx_reg, cache); + if (!do_init) + return 0; + + log_debug("Initializing host %d..%d:", drive->ata_port, + drive->ata_port + 3); + log_debug("\tbuffer: %s", strstr(path, "/ata")); + + rc = _write_amd_register(path, drive); + if (rc) + return rc; + + rc = _write_cfg_register(path, cache, IBPI_PATTERN_NONE); + if (rc) + return rc; + + return _write_tx_register(path, &tx_reg); +} + +static int _amd_sgpio_init(const char *path) +{ + int rc; + char em_path[PATH_MAX+10]; /* 10 == strlen("/em_buffer") */ + struct amd_drive drive; + struct cache_entry *cache; + struct cache_entry cache_dup; + + snprintf(em_path, PATH_MAX+10, "%s/em_buffer", path); + + rc = _get_amd_sgpio_drive(em_path, &drive); + if (rc) { + log_error("Couldn't find drive info for %s\n", em_path); + return rc; + } + + cache = _get_cache(&drive); + if (!cache) { + log_error("Couldn't retrieve cache"); + return -1; + } + + /* Save copy of cache entry */ + memcpy(&cache_dup, cache, sizeof(cache_dup)); + + rc = _amd_sgpio_init_one(em_path, &drive, cache); + if (rc) { + log_error("SGPIO register init failed for bank %d, %s", + drive.initiator, em_path); + + /* Restore saved cache entry */ + memcpy(cache, &cache_dup, sizeof(*cache)); + + goto _init_amd_sgpio_err; + } + + _put_cache(); + + /* AMD uses SGPIO registers to control drive LEDs in sets of 8 + * drives. The initiator bit in the amd register controls which + * set of four drives (0-3 or 4-7) the transmit register is + * updating. + * + * When initializing the registers we want to do all 8 drives + * so we need to reset the drive ata_port and initiator values. + */ + if (drive.initiator) { + drive.ata_port -= 4; + drive.initiator = 0; + } else { + drive.ata_port += 4; + drive.initiator = 1; + } + + cache = _get_cache(&drive); + if (!cache) { + log_error("Couldn't retrieve cache"); + return -1; + } + + /* Save copy of cache entry */ + memcpy(&cache_dup, cache, sizeof(cache_dup)); + + rc = _amd_sgpio_init_one(em_path, &drive, cache); + if (rc) { + log_error("SGPIO register init failed for bank %d, %s", + drive.initiator, em_path); + + /* Restore saved cache entry */ + memcpy(cache, &cache_dup, sizeof(*cache)); + } + +_init_amd_sgpio_err: + _put_cache(); + return rc; +} + +int _amd_sgpio_em_enabled(const char *path) +{ + char *p; + int rc, found; + uint32_t caps; + char em_path[PATH_MAX]; + + /* Check that libahci module was loaded with ahci_em_messages=1 */ + p = get_text("/sys/module/libahci/parameters", "ahci_em_messages"); + if (!p || (p && *p == 'N')) { + log_info("Kernel libahci module enclosure management messaging not enabled.\n"); + if (p) + free(p); + return 0; + } + + free(p); + + /* Find base path for enclosure management */ + found = _find_file_path(path, "em_buffer", em_path, PATH_MAX); + if (!found) { + log_info("Couldn't find base EM path for %s\n", path); + return 0; + } + + /* Validate that enclosure management is supported */ + p = get_text(em_path, "em_message_supported"); + if (!p) { + log_info("Couldn't get 'em_messages_supported' for %s", + path); + return 0; + } + + if (strstr(p, "sgpio") == NULL) { + log_info("SGPIO EM not supported for %s\n", path); + free(p); + return 0; + } + + free(p); + + /* Verify host enclosure management capabilities */ + p = get_text(em_path, "ahci_host_caps"); + if (!p) { + log_info("Couldn't read host capabilities for %s\n", path); + return 0; + } + + rc = sscanf(p, "%" SCNx32, &caps); + free(p); + if (rc <= 0) { + log_info("Couldn't parse host capabilities for %s", path); + return 0; + } + + if (!(caps & HOST_CAP_EMS)) { + log_info("EM not supported for %s", path); + return 0; + } + + rc = _amd_sgpio_init(em_path); + return rc ? 0 : 1; +} + +int _amd_sgpio_write(struct block_device *device, enum ibpi_pattern ibpi) +{ + /* write only if state has changed */ + if (ibpi == device->ibpi_prev) + return 1; + + if ((ibpi < IBPI_PATTERN_NORMAL) || (ibpi > IBPI_PATTERN_LOCATE_OFF)) + __set_errno_and_return(ERANGE); + + if ((ibpi == IBPI_PATTERN_DEGRADED) || + (ibpi == IBPI_PATTERN_FAILED_ARRAY)) + __set_errno_and_return(ENOTSUP); + + return _set_ibpi(device, ibpi); +} + +char *_amd_sgpio_get_path(const char *cntrl_path) +{ + int len, found; + char *em_buffer_path; + char tmp[PATH_MAX]; + + em_buffer_path = malloc(PATH_MAX); + if (!em_buffer_path) { + log_error("Couldn't allocate memory to get path for %s\n%s", + cntrl_path, strerror(errno)); + return NULL; + } + + found = _find_file_path(cntrl_path, "em_buffer", tmp, PATH_MAX); + if (!found) { + log_error("Couldn't find EM buffer for %s\n", cntrl_path); + free(em_buffer_path); + return NULL; + } + + len = snprintf(em_buffer_path, PATH_MAX, "%s/em_buffer", tmp); + if (len < 0 || len >= PATH_MAX) { + free(em_buffer_path); + return NULL; + } + + return em_buffer_path; +} diff --git a/src/amd_sgpio.h b/src/amd_sgpio.h new file mode 100644 index 0000000..bd14e35 --- /dev/null +++ b/src/amd_sgpio.h @@ -0,0 +1,24 @@ +/* + * AMD SGPIO LED control + * Copyright (C) 2019, Advanced Micro Devices, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#include "block.h" + +int _amd_sgpio_em_enabled(const char *path); +int _amd_sgpio_write(struct block_device *device, enum ibpi_pattern ibpi); +char *_amd_sgpio_get_path(const char *cntrl_path); diff --git a/src/block.c b/src/block.c new file mode 100644 index 0000000..e4f5b8c --- /dev/null +++ b/src/block.c @@ -0,0 +1,423 @@ +/* + * Intel(R) Enclosure LED Utilities + * Copyright (C) 2009-2020 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if _HAVE_DMALLOC_H +#include +#endif + +#include "ahci.h" +#include "block.h" +#include "config.h" +#include "dellssd.h" +#include "pci_slot.h" +#include "raid.h" +#include "scsi.h" +#include "slave.h" +#include "smp.h" +#include "status.h" +#include "sysfs.h" +#include "utils.h" +#include "vmdssd.h" +#include "npem.h" +#include "amd.h" + +/* Global timestamp value. It shell be used to update a timestamp field of block + device structure. See block.h for details. */ +time_t timestamp = 0; + +/** + * @brief Determines if disk is attached directly or via expander + */ +int dev_directly_attached(const char *path) +{ + if (strstr(path, "/expander") == 0) + return 1; + return 0; +} + +/** + * @brief Determines a send function. + * + * This is the internal function of 'block device' module. The function tries to + * determine a LED management protocol based on controller type and the given + * path to block device in sysfs tree. First it checks whether to use + * the default send function. If not it tries to read the content + * of em_message_type field from sysfs tree and determines + * the LED control protocol. + * + * @param[in] cntrl type of a controller a device is connected to. + * @param[in] path path to a block device in sysfs tree. + * + * @return Pointer to send message function if successful, otherwise the function + * returns the NULL pointer and it means either the controller does not + * support enclosure management or LED control protocol + * is not supported. + */ +static send_message_t _get_send_fn(struct cntrl_device *cntrl, const char *path) +{ + send_message_t result = NULL; + + if (cntrl->cntrl_type == CNTRL_TYPE_AHCI) { + result = ahci_sgpio_write; + } else if (cntrl->cntrl_type == CNTRL_TYPE_SCSI + && !dev_directly_attached(path)) { + result = scsi_ses_write; + } else if (cntrl->cntrl_type == CNTRL_TYPE_SCSI + && dev_directly_attached(path)) { + result = scsi_smp_fill_buffer; + } else if (cntrl->cntrl_type == CNTRL_TYPE_DELLSSD) { + result = dellssd_write; + } else if (cntrl->cntrl_type == CNTRL_TYPE_VMD) { + result = vmdssd_write; + } else if (cntrl->cntrl_type == CNTRL_TYPE_NPEM) { + result = npem_write; + } else if (cntrl->cntrl_type == CNTRL_TYPE_AMD) { + result = amd_write; + } + return result; +} + +static int do_not_flush(struct block_device *device __attribute__ ((unused))) +{ + return 1; +} + +static flush_message_t _get_flush_fn(struct cntrl_device *cntrl, const char *path) +{ + flush_message_t result = NULL; + + if (cntrl->cntrl_type == CNTRL_TYPE_SCSI) { + if (dev_directly_attached(path)) + result = scsi_smp_write_buffer; + else + result = scsi_ses_flush; + } else { + result = do_not_flush; + } + return result; +} + +/** + * @brief Determines a host path to block device. + * + * This is the internal function of 'block device' module. The function + * determines a host path to block device in sysfs. + * + * @param[in] path path to block device in sysfs. + * @param[in] cntrl controller device the block + * device is connected to. + * + * @return Pointer to memory block containing a host path. The memory block + * should be freed if one don't need the content. + */ +static char *_get_host(char *path, struct cntrl_device *cntrl) +{ + char *result = NULL; + + if (cntrl->cntrl_type == CNTRL_TYPE_SCSI) + result = scsi_get_slot_path(path, cntrl->sysfs_path); + else if (cntrl->cntrl_type == CNTRL_TYPE_AHCI) + result = ahci_get_port_path(path); + else if (cntrl->cntrl_type == CNTRL_TYPE_DELLSSD) + result = dellssd_get_path(cntrl->sysfs_path); + else if (cntrl->cntrl_type == CNTRL_TYPE_VMD) + result = vmdssd_get_path(cntrl->sysfs_path); + else if (cntrl->cntrl_type == CNTRL_TYPE_NPEM) + result = npem_get_path(cntrl->sysfs_path); + else if (cntrl->cntrl_type == CNTRL_TYPE_AMD) + result = amd_get_path(path, cntrl->sysfs_path); + + return result; +} + +static int is_host_id_supported(const struct block_device *bd) +{ + if (!bd->cntrl) + return 0; + + switch (bd->cntrl->cntrl_type) { + case CNTRL_TYPE_DELLSSD: + case CNTRL_TYPE_VMD: + case CNTRL_TYPE_NPEM: + return 0; + default: + return 1; + } +} +/** + * @brief Determines a storage controller. + * + * This is the internal function of 'block device' module. The function gets + * a pointer to controller structure the device is connected to. + * + * @param[in] cntrl_list pointer to list of supported controllers. + * @param[in] path path to block device in sysfs tree. + * + * @return Pointer to controller structure if successful, otherwise the function + * returns NULL pointer. The NULL pointer means that block devices is + * connected to unsupported storage controller. + */ +struct cntrl_device *block_get_controller(const struct list *cntrl_list, char *path) +{ + struct cntrl_device *cntrl; + struct cntrl_device *non_npem_cntrl = NULL; + + list_for_each(cntrl_list, cntrl) { + if (strncmp(cntrl->sysfs_path, path, + strlen(cntrl->sysfs_path)) == 0) { + if (cntrl->cntrl_type == CNTRL_TYPE_NPEM) + return cntrl; + non_npem_cntrl = cntrl; + } + } + return non_npem_cntrl; +} + +struct _host_type *block_get_host(struct cntrl_device *cntrl, int host_id) +{ + struct _host_type *hosts = NULL; + + if (!cntrl) + return hosts; + + hosts = cntrl->hosts; + while (hosts) { + if (hosts->host_id == host_id) + break; + hosts = hosts->next; + } + return hosts; +} + +/* + * Allocates a new block device structure. See block.h for details. + */ +struct block_device *block_device_init(const struct list *cntrl_list, const char *path) +{ + struct cntrl_device *cntrl; + char link[PATH_MAX]; + char *host = NULL; + struct block_device *device = NULL; + struct pci_slot *pci_slot = NULL; + send_message_t send_fn = NULL; + flush_message_t flush_fn = NULL; + int host_id = -1; + char *host_name; + + if (realpath(path, link)) { + pci_slot = vmdssd_find_pci_slot(link); + cntrl = block_get_controller(cntrl_list, link); + if (cntrl != NULL) { + if (cntrl->cntrl_type == CNTRL_TYPE_VMD && !pci_slot) + return NULL; + host = _get_host(link, cntrl); + if (host == NULL) + return NULL; + host_name = get_path_hostN(link); + if (host_name) { + if (sscanf(host_name, "host%d", &host_id) != 1) + host_id = -1; + free(host_name); + } + flush_fn = _get_flush_fn(cntrl, link); + send_fn = _get_send_fn(cntrl, link); + if (send_fn == NULL) { + free(host); + return NULL; + } + } else { + return NULL; + } + + device = calloc(1, sizeof(*device)); + if (device) { + struct _host_type *hosts = cntrl ? cntrl->hosts : NULL; + + device->cntrl = cntrl; + device->sysfs_path = str_dup(link); + device->cntrl_path = host; + device->ibpi = IBPI_PATTERN_UNKNOWN; + device->ibpi_prev = IBPI_PATTERN_NONE; + device->send_fn = send_fn; + device->flush_fn = flush_fn; + device->timestamp = timestamp; + device->host = NULL; + device->host_id = host_id; + device->encl_index = -1; + device->raid_dev = NULL; + while (hosts) { + if (hosts->host_id == host_id) { + device->host = hosts; + break; + } + hosts = hosts->next; + } + if (cntrl && cntrl->cntrl_type == CNTRL_TYPE_SCSI) { + device->phy_index = cntrl_init_smp(link, cntrl); + if (!dev_directly_attached(link) + && !scsi_get_enclosure(device)) { + log_debug("Device initialization failed for '%s'", + path); + free(device->sysfs_path); + free(device->cntrl_path); + free(device); + device = NULL; + } + } + } else if (host) { + free(host); + } + } + return device; +} + +/** + * Frees memory allocated for block device structure. See block.h for details. + */ +void block_device_fini(struct block_device *device) +{ + if (device) { + if (device->sysfs_path) + free(device->sysfs_path); + + if (device->cntrl_path) + free(device->cntrl_path); + + if (device->raid_dev) + raid_device_fini(device->raid_dev); + + free(device); + } +} + +/* + * Duplicates a block device structure. See block.h for details. + */ +struct block_device *block_device_duplicate(struct block_device *block) +{ + struct block_device *result = NULL; + + if (block) { + result = calloc(1, sizeof(*result)); + if (result) { + result->sysfs_path = str_dup(block->sysfs_path); + result->cntrl_path = str_dup(block->cntrl_path); + if (block->ibpi != IBPI_PATTERN_UNKNOWN) + result->ibpi = block->ibpi; + else + result->ibpi = IBPI_PATTERN_ONESHOT_NORMAL; + result->ibpi_prev = block->ibpi_prev; + result->send_fn = block->send_fn; + result->flush_fn = block->flush_fn; + result->timestamp = block->timestamp; + result->cntrl = block->cntrl; + result->host = block->host; + result->host_id = block->host_id; + result->phy_index = block->phy_index; + result->encl_index = block->encl_index; + result->enclosure = block->enclosure; + result->raid_dev = + raid_device_duplicate(block->raid_dev); + } + } + return result; +} + +int block_compare(const struct block_device *bd_old, + const struct block_device *bd_new) +{ + int i = 0; + + if (is_host_id_supported(bd_old) && bd_old->host_id == -1) { + log_debug("Device %s : No host_id!", + strstr(bd_old->sysfs_path, "host")); + return 0; + } + if (is_host_id_supported(bd_new) && bd_new->host_id == -1) { + log_debug("Device %s : No host_id!", + strstr(bd_new->sysfs_path, "host")); + return 0; + } + + if (bd_old->cntrl->cntrl_type != bd_new->cntrl->cntrl_type) + return 0; + + switch (bd_old->cntrl->cntrl_type) { + case CNTRL_TYPE_AHCI: + /* Missing support for port multipliers. Compare just hostX. */ + i = (bd_old->host_id == bd_new->host_id); + break; + + case CNTRL_TYPE_SCSI: + /* Host and phy is not enough. They might be DA or EA. */ + if (dev_directly_attached(bd_old->sysfs_path) && + dev_directly_attached(bd_new->sysfs_path)) { + /* Just compare host & phy */ + i = (bd_old->host_id == bd_new->host_id) && + (bd_old->phy_index == bd_new->phy_index); + break; + } + if (!dev_directly_attached(bd_old->sysfs_path) && + !dev_directly_attached(bd_new->sysfs_path)) { + /* Both expander attached */ + i = (bd_old->host_id == bd_new->host_id) && + (bd_old->phy_index == bd_new->phy_index); + i = i && (bd_old->enclosure == bd_new->enclosure); + i = i && (bd_old->encl_index == bd_new->encl_index); + break; + } + /* */ + break; + + case CNTRL_TYPE_VMD: + /* compare names and address of the drive */ + i = (strcmp(bd_old->sysfs_path, bd_new->sysfs_path) == 0); + if (!i) { + struct pci_slot *old_slot, *new_slot; + + old_slot = vmdssd_find_pci_slot(bd_old->sysfs_path); + new_slot = vmdssd_find_pci_slot(bd_new->sysfs_path); + if (old_slot && new_slot) + i = (strcmp(old_slot->address, new_slot->address) == 0); + } + break; + + case CNTRL_TYPE_NPEM: + /* check controller to determine slot. */ + i = (strcmp(bd_old->cntrl_path, bd_new->cntrl_path) == 0); + break; + + case CNTRL_TYPE_DELLSSD: + default: + /* Just compare names */ + i = (strcmp(bd_old->sysfs_path, bd_new->sysfs_path) == 0); + break; + } + return i; +} diff --git a/src/block.h b/src/block.h new file mode 100644 index 0000000..820f2d7 --- /dev/null +++ b/src/block.h @@ -0,0 +1,230 @@ +/* + * Intel(R) Enclosure LED Utilities + * Copyright (C) 2009-2018 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#ifndef _BLOCK_H_INCLUDED_ +#define _BLOCK_H_INCLUDED_ + +#include "cntrl.h" +#include "ibpi.h" +#include "time.h" +#include "list.h" +#include "raid.h" + +struct block_device; + +/** + * @brief Pointer to a send message function. + * + * The pointer to a function which knows how to send LED message to a driver of + * storage controller using the given protocol. + * + * @param[in] path path in sysfs to a host device + * @see block_device::cntrl_path. + * @param[in] ibpi an IBPI pattern (state) to visualize. + * + * @return 1 if successful, otherwise the function returns 0. + */ +typedef int (*send_message_t) (struct block_device *device, + enum ibpi_pattern ibpi); + +/** + * @brief Pointer to a flush buffer function. + * + * @param[in] device pointer to a block device + * + * @return 1 if successful, otherwise the function returns 0. + */ +typedef int (*flush_message_t) (struct block_device *device); + +/** + * @brief Describes a block device. + * + * This structure describes a block device. It does not describe virtual devices + * or partitions on physical block devices. + */ +struct block_device { +/** + * Real path in sysfs tree. This means i.e. if /sys/block/sda is symbolic link + * then the link will be read and path stored in sysfs_path field. This path + * may not exist in sysfs if connection to physical drive is lost. This filed + * cannot have NULL pointer assigned. + */ + char *sysfs_path; + +/** + * The pointer to a function which sends a message to driver in order to + * control LEDs in an enclosure or DAS system - @see send_message_t for details. + * This field cannot have NULL pointer assigned. + */ + send_message_t send_fn; + +/** + * The pointer to a function which flush buffers filled by send_fn. + */ + flush_message_t flush_fn; + +/** + * Canonical path to block device where enclosure management fields are located. + * This path is always accessible even if the connection to physical device + * is lost. In case of AHCI controller it points to SATA phy. In case of SAS + * this path points to SES entry associated with the slot in an enclosure. + * This field cannot have NULL pointer assign. + */ + char *cntrl_path; + +/** + * The current state of block device. This is an IBPI pattern and it is used + * to visualize the state of block device. + */ + enum ibpi_pattern ibpi; + +/** + * The previous state of block device. + */ + enum ibpi_pattern ibpi_prev; + +/** + * The time stamp used to determine if the given block device still exist or + * it failed and the device is no longer available. Every time IBPI pattern + * is updated, the time-stamp is updated, too. + */ + time_t timestamp; + +/** + * The pointer to storage controller structure the device is connected to. + */ + struct cntrl_device *cntrl; + + struct _host_type *host; + + int host_id; + +/** + * The index of phy utilized by directly attached to controller block device. + * It is meaningful if device is controlled by isci driver. + */ + int phy_index; + +/** + * The index in Enclosure. This is what should be used when using SES-2. + */ + int encl_index; + + struct enclosure_device *enclosure; + +/** + * If disk is a raid member, this field will be set with a copy of raid device + * struct. + */ + struct raid_device *raid_dev; +}; + +/** + * @brief Creates a block device structure. + * + * This function allocates memory for a new structure of block device. It reads + * the sysfs entries and populates the structure fields. It performs all this + * actions only if the block device is connected to the one of supported storage + * controllers and the controller has enclosure management services enabled. + * + * @param[in] cntrl_list pointer to a list of supported controller + * devices. + * @param[in] sysfs_path a path to block device in sysfs. + * + * @return Pointer to block device structure if successful, otherwise the function + * returns the NULL pointer. + */ +struct block_device *block_device_init(const struct list *cntrl_list, const char *path); + +/** + * @brief Releases a block device structure. + * + * This function releases memory allocated for block device structure. + * + * @param[in] device pointer to block device structure. + * + * @return The function does not return a value. + */ +void block_device_fini(struct block_device *device); + +/** + * @brief Duplicates a block device structure. + * + * The function allocates memory for a block device structure and copies values + * stored in fields of source block structure. The function allocates new memory + * for all string fields in a copy structure. It is safe to release source block + * structure just after it has been duplicated. + * + * @param[in] device pointer to source block device structure. + * + * @return Pointer to block device structure if successful, otherwise the function + * returns the NULL pointer. + */ +struct block_device *block_device_duplicate(struct block_device *device); + +/** + * @brief Determines a storage controller. + * + * This is the internal function of 'block device' module. The function gets + * a pointer to controller structure the device is connected to. + * + * @param[in] cntrl_list pointer to list of supported controllers. + * @param[in] path path to block device in sysfs tree. + * + * @return Pointer to controller structure if successful, otherwise the function + * returns NULL pointer. The NULL pointer means that block devices is + * connected to unsupported storage controller. + */ +struct cntrl_device *block_get_controller(const struct list *cntrl_list, char *path); + +/** + * The global timestamp variable. It is updated every time the sysfs is scanning + * by an application. The value stored in this variable should be used to update + * all timestamp stored in block device structures. + */ +extern time_t timestamp; + +/** + * @brief Determines if block device is attached directly or via expander + */ +int dev_directly_attached(const char *path); + +/** + * @brief Gets the host structure for given control device and host_id + */ +struct _host_type *block_get_host(struct cntrl_device *cntrl, int host_id); + + +/** + * @brief Checks the presence of block device. + * + * This is internal function of monitor service. The function is checking + * whether block device is already on the list or it is missing from the list. + * The function is design to be used as 'test' parameter for list_find_first() + * function. + * + * @param[in] bd_old - an element from a list to compare to. + * @param[in] bd_new - a block device being searched. + * + * @return 0 if the block devices do not match, otherwise function returns 1. + */ +int block_compare(const struct block_device *bd_old, + const struct block_device *bd_new); + +#endif /* _BLOCK_H_INCLUDED_ */ diff --git a/src/cntrl.c b/src/cntrl.c new file mode 100644 index 0000000..409e345 --- /dev/null +++ b/src/cntrl.c @@ -0,0 +1,476 @@ +/* + * Intel(R) Enclosure LED Utilities + * Copyright (C) 2009-2020 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#include +#include +#include +#include +#include +#include + +#if _HAVE_DMALLOC_H +#include +#endif + +#include "cntrl.h" +#include "config.h" +#include "config_file.h" +#include "list.h" +#include "smp.h" +#include "status.h" +#include "sysfs.h" +#include "utils.h" +#include "amd.h" +#include "npem.h" + +/** + * @brief Name of controllers types. + * + * This is internal array with names of controller types. Array can be use to + * translate enumeration type into the string. + */ +static const char * const ctrl_type_str[] = { + [CNTRL_TYPE_UNKNOWN] = "?", + [CNTRL_TYPE_DELLSSD] = "Dell SSD", + [CNTRL_TYPE_VMD] = "VMD", + [CNTRL_TYPE_SCSI] = "SCSI", + [CNTRL_TYPE_AHCI] = "AHCI", + [CNTRL_TYPE_NPEM] = "NPEM", + [CNTRL_TYPE_AMD] = "AMD", +}; + +/** + */ +static int _is_storage_controller(const char *path) +{ + uint64_t class = get_uint64(path, 0, "class"); + return (class & 0xff0000L) == 0x010000L; +} + +/** + */ +static int _is_isci_cntrl(const char *path) +{ + return sysfs_check_driver(path, "isci"); +} + +/** + */ +static int _is_cntrl(const char *path, const char *type) +{ + char temp[PATH_MAX], link[PATH_MAX], *t; + + snprintf(temp, sizeof(temp), "%s/driver", path); + + if (realpath(temp, link) == NULL) + return 0; + + t = strrchr(link, '/'); + if ((t != NULL) && (strcmp(t + 1, type) != 0)) + return 0; + + return 1; +} + +static int _is_ahci_cntrl(const char *path) +{ + return _is_cntrl(path, "ahci"); +} + +static int _is_nvme_cntrl(const char *path) +{ + return _is_cntrl(path, "nvme"); +} + +static int _is_intel_ahci_cntrl(const char *path) +{ + if (!_is_ahci_cntrl(path)) + return 0; + + return get_uint64(path, 0, "vendor") == 0x8086L; +} + +static int _is_amd_ahci_cntrl(const char *path) +{ + if (!_is_ahci_cntrl(path)) + return 0; + + return get_uint64(path, 0, "vendor") == 0x1022L; +} + +static int _is_amd_nvme_cntrl(const char *path) +{ + char tmp[PATH_MAX]; + char *t; + + if (!_is_nvme_cntrl(path)) + return 0; + + sprintf(tmp, "%s", path); + t = strrchr(tmp, '/'); + if (!t) + return 0; + + t++; + *t = '\0'; + return get_uint64(tmp, 0, "vendor") == 0x1022L; +} + +static int _is_amd_cntrl(const char *path) +{ + if (_is_amd_ahci_cntrl(path)) + return 1; + + if (_is_amd_nvme_cntrl(path)) + return 1; + + return 0; +} + +extern int get_dell_server_type(void); + +static int _is_dellssd_cntrl(const char *path) +{ + uint64_t vdr, dev, svdr, cls; + int gen = 0; + + vdr = get_uint64(path, 0, "vendor"); + dev = get_uint64(path, 0, "device"); + cls = get_uint64(path, 0, "class"); + svdr = get_uint64(path, 0, "subsystem_vendor"); + if (cls == 0x10802) + gen = get_dell_server_type(); + + return ((vdr == 0x1344L && dev == 0x5150L) || /* micron ssd */ + (gen != 0) || /* Dell Server+NVME */ + (svdr == 0x1028 && cls == 0x10802)); /* nvmhci ssd */ +} + +/** + */ +static int _is_smp_cntrl(const char *path) +{ + int result = 0; + struct list dir; + char *p; + char host_path[PATH_MAX] = { 0 }; + if (scan_dir(path, &dir) == 0) { + const char *dir_path; + + list_for_each(&dir, dir_path) { + p = strrchr(dir_path, '/'); + if (!p++) + break; + if (strncmp(p, "host", strlen("host")) == 0) { + snprintf(host_path, sizeof(host_path), + "%s/%s/bsg/sas_%s", path, p, p); + result = smp_write_gpio(host_path, + GPIO_REG_TYPE_TX, + 0, + 0, + "", + 0) == 0; + } + } + list_erase(&dir); + } + + return result; +} + +static int _is_vmd_cntrl(const char *path) +{ + return sysfs_check_driver(path, "vmd"); +} + +static int _is_npem_cntrl(const char *path) +{ + return is_npem_capable(path); +} + +/** + * @brief Determines the type of controller. + * + * This is internal function of 'controller device' module. The function + * determines the type of controller device. It might be AHCI, SCSI or + * UNKNOWN device type. + * + * @param[in] path path to controller device in sysfs tree. + * + * @return The type of controller device. If the type returned is + * CNTRL_TYPE_UNKNOWN this means a controller device is not + * supported. + */ +static enum cntrl_type _get_type(const char *path) +{ + enum cntrl_type type = CNTRL_TYPE_UNKNOWN; + if (_is_npem_cntrl(path)) { + type = CNTRL_TYPE_NPEM; + } else if (_is_vmd_cntrl(path)) { + type = CNTRL_TYPE_VMD; + } else if (_is_dellssd_cntrl(path)) { + type = CNTRL_TYPE_DELLSSD; + } else if (_is_storage_controller(path)) { + if (_is_intel_ahci_cntrl(path)) + type = CNTRL_TYPE_AHCI; + else if (_is_amd_cntrl(path)) + type = CNTRL_TYPE_AMD; + else if (_is_isci_cntrl(path) + || sysfs_enclosure_attached_to_cntrl(path) + || _is_smp_cntrl(path)) + type = CNTRL_TYPE_SCSI; + } + return type; +} + +struct _host_type *alloc_host(int id, struct _host_type *next) +{ + struct _host_type *host = NULL; + host = malloc(sizeof(struct _host_type)); + if (host) { + host->host_id = id; + host->ibpi_state_buffer = NULL; + memset(host->bitstream, 0, sizeof(host->bitstream)); + host->flush = 0; + host->ports = 0; + host->next = next; + } + return host; +} + +void free_hosts(struct _host_type *h) +{ + struct _host_type *t; + while (h) { + t = h->next; + free(h->ibpi_state_buffer); + free(h); + h = t; + } +} + +void _find_host(const char *path, struct _host_type **hosts) +{ + const int host_len = sizeof("host") - 1; + char *p; + int index = -1; + struct _host_type *th; + DIR *d; + struct dirent *de; + + p = strrchr(path, '/'); + if (!p++) + return; + if (strncmp(p, "host", host_len - 1) == 0) { + index = atoi(p + host_len); + th = alloc_host(index, (*hosts) ? (*hosts) : NULL); + if (!th) + return; + + d = opendir(path); + if(!d) { + free(th); + return; + } + + while ((de = readdir(d))) { + if (strncmp(de->d_name, "phy-", strlen("phy-")) == 0) { + th->ports++; + } + } + + closedir(d); + + *hosts = th; + } +} + +/** + * @brief Get all instances of separate hosts on isci controller. + * + */ +static struct _host_type *_cntrl_get_hosts(const char *path) +{ + struct _host_type *hosts = NULL; + struct list dir; + if (scan_dir(path, &dir) == 0) { + const char *dir_path; + + list_for_each(&dir, dir_path) + _find_host(dir_path, &hosts); + list_erase(&dir); + } + return hosts; +} + +/** + * @brief Check if enclosure management is enabled. + * + * This is internal function of 'controller device' module. The function checks + * whether enclosure management is enabled for AHCI controller. + * + * @param[in] path path to controller device in sysfs tree. + * + * @return 1 if enclosure management is enabled, otherwise the function returns 0. + */ +static unsigned int _ahci_em_messages(const char *path) +{ + /* first, open ...driver/module/parameters/ahci_em_messages + * then open /sys/module/libahci/parameters/ahci_em_messages + * and check if it is set to enabled. + * if so, check if 'holders' of libahci points the same driver name + * as device given by path + */ + char buf[PATH_MAX]; + char *link, *name; + DIR *dh; + struct dirent *de = NULL; + + /* old kernel (prior to 2.6.36) */ + if (get_int(path, 0, "driver/module/parameters/ahci_em_messages") != 0) + return 1; + + /* parameter type changed from int to bool since kernel v3.13 */ + if (!get_int("", 0, "sys/module/libahci/parameters/ahci_em_messages")) { + if (!get_bool("", 0, "sys/module/libahci/parameters/ahci_em_messages")) + return 0; + } + + if (snprintf(buf, sizeof(buf), "%s/%s", path, "driver") < 0) + return 0; + + link = realpath(buf, NULL); + if (!link) + return 0; + + name = strrchr(link, '/'); + if (!name++) { + free(link); + return 0; + } + + /* check if the directory /sys/module/libahci/holders exists */ + dh = opendir("/sys/module/libahci/holders"); + if (dh) { + /* name contain controller name (ie. ahci),*/ + /* so check if libahci holds this driver */ + while ((de = readdir(dh))) { + if (!strcmp(de->d_name, name)) + break; + } + closedir(dh); + free(link); + return de ? 1 : 0; + } else { + free(link); + return 1; + } +} + +/* + * Allocates memory for a new controller device structure. See cntrl.h for + * details. + */ +struct cntrl_device *cntrl_device_init(const char *path) +{ + unsigned int em_enabled; + enum cntrl_type type; + struct cntrl_device *device = NULL; + + type = _get_type(path); + if (type != CNTRL_TYPE_UNKNOWN) { + switch (type) { + case CNTRL_TYPE_DELLSSD: + case CNTRL_TYPE_SCSI: + case CNTRL_TYPE_VMD: + case CNTRL_TYPE_NPEM: + em_enabled = 1; + break; + case CNTRL_TYPE_AHCI: + em_enabled = _ahci_em_messages(path); + break; + case CNTRL_TYPE_AMD: + em_enabled = amd_em_enabled(path); + break; + default: + em_enabled = 0; + } + if (em_enabled) { + if (!list_is_empty(&conf.cntrls_whitelist)) { + char *cntrl = NULL; + + list_for_each(&conf.cntrls_whitelist, cntrl) { + if (match_string(cntrl, path)) + break; + cntrl = NULL; + } + if (!cntrl) { + log_debug("%s not found on whitelist, ignoring", path); + return NULL; + } + } else if (!list_is_empty(&conf.cntrls_blacklist)) { + char *cntrl; + + list_for_each(&conf.cntrls_blacklist, cntrl) { + if (match_string(cntrl, path)) { + log_debug("%s found on blacklist, ignoring", + path); + return NULL; + } + } + } + device = malloc(sizeof(struct cntrl_device)); + if (device) { + if (type == CNTRL_TYPE_SCSI) { + device->isci_present = _is_isci_cntrl(path); + device->hosts = _cntrl_get_hosts(path); + } else { + device->isci_present = 0; + device->hosts = NULL; + } + device->cntrl_type = type; + device->sysfs_path = str_dup(path); + } + } else { + log_error + ("controller discovery: %s - enclosure " \ + "management not supported.", path); + } + } + return device; +} + +/* + * Frees memory allocated for controller device structure. See cntrl.h for + * details. + */ +void cntrl_device_fini(struct cntrl_device *device) +{ + if (device) { + free(device->sysfs_path); + free_hosts(device->hosts); + free(device); + } +} + +void print_cntrl(struct cntrl_device *ctrl_dev) +{ + printf("%s (%s)\n", ctrl_dev->sysfs_path, + ctrl_type_str[ctrl_dev->cntrl_type]); +} diff --git a/src/cntrl.h b/src/cntrl.h new file mode 100644 index 0000000..ff1a3ec --- /dev/null +++ b/src/cntrl.h @@ -0,0 +1,124 @@ +/* + * Intel(R) Enclosure LED Utilities + * Copyright (C) 2009-2020 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#ifndef _CNTRL_H_INCLUDED_ +#define _CNTRL_H_INCLUDED_ + +/** + * This enumeration type lists all supported storage controller types. + */ +enum cntrl_type { + CNTRL_TYPE_UNKNOWN = 0, + CNTRL_TYPE_DELLSSD, + CNTRL_TYPE_VMD, + CNTRL_TYPE_SCSI, + CNTRL_TYPE_AHCI, + CNTRL_TYPE_NPEM, + CNTRL_TYPE_AMD, +}; + +/** + * @brief Storage controller device structure. + * + * This structure describes a storage controller device existing in the system. + */ +struct cntrl_device { + /** + * Path to the device in sysfs tree. + */ + char *sysfs_path; + + /** + * Type of storage controller device. + */ + enum cntrl_type cntrl_type; + + /** + * Flag if scsi controller driver is "isci" + */ + int isci_present; + + struct _host_type { + /** + * ibpi state buffer for directly attached devices + */ + struct gpio_tx_register_byte *ibpi_state_buffer; + /** + * outbound raw byte stream + */ + unsigned char bitstream[4]; + /** + * bitstream's flush flag + */ + int flush; + /** + * host identifier for different hba instances + */ + int host_id; + /** + * number of total phy ports + */ + int ports; + /** + * pointer to next structure + */ + struct _host_type *next; + } *hosts; +}; + +/** + * @brief Allocates a new controller device structure. + * + * This function allocates memory for a new structure of storage controller + * device. It reads the sysfs entries and populates structure fields. + * The function registers only supported storage controllers. + * + * @param[in] path path to storage controller in sysfs tree. + * + * @return Pointer to storage controller structure if successful, otherwise the + * function returns NULL pointer. The NULL pointer means that controller + * device is not supported. + */ +struct cntrl_device *cntrl_device_init(const char *path); + +/** + * @brief Releases a controller device structure. + * + * This function releases memory allocated for controller device structure. + * + * @param[in] device pointer to controller device structure. + * + * @return The function does not return a value. + */ +void cntrl_device_fini(struct cntrl_device *device); + +/** + * @brief Prints given controller to stdout. + * + * This function prints the path and type of controller device given as + * argument. + * + * @param[in] ctrl_dev address to element from a + * controller list. + * + * @return The function does not return a value. + */ +void print_cntrl(struct cntrl_device *ctrl_dev); + +#endif /* _CNTRL_H_INCLUDED_ */ diff --git a/src/config_file.c b/src/config_file.c new file mode 100644 index 0000000..7141905 --- /dev/null +++ b/src/config_file.c @@ -0,0 +1,359 @@ +/* + * Intel(R) Enclosure LED Utilities + * + * Copyright (C) 2017-2019 Intel Corporation. + * Copyright (C) 2009 Karel Zak + * + * SPDX-License-Identifier: GPL-2.0 + * + * Contains code from util-linux/libblkid/src/config.c + * originally released under LGPL. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config_file.h" +#include "utils.h" +#include "status.h" + + +/** + * @brief Pointer to global ledmon configuration. + * + * This is a pointer to structure that contains settings of ledmon behavior + * read from configuration file. + */ +struct ledmon_conf conf; + +const char *log_level_map[] = { + [LOG_LEVEL_QUIET] = "QUIET", + [LOG_LEVEL_ERROR] = "ERROR", + [LOG_LEVEL_WARNING] = "WARNING", + [LOG_LEVEL_INFO] = "INFO", + [LOG_LEVEL_DEBUG] = "DEBUG", + [LOG_LEVEL_ALL] = "ALL" +}; + +static int parse_bool(char *s) +{ + if (*s && (!strcasecmp(s, "enabled") || + !strcasecmp(s, "true") || + !strcasecmp(s, "yes") || + !strcasecmp(s, "1"))) + return 1; + else if (*s && (!strcasecmp(s, "disabled") || + !strcasecmp(s, "false") || + !strcasecmp(s, "no") || + !strcasecmp(s, "0"))) + return 0; + + fprintf(stderr, "Unknown bool value: %s\n", s); + return -1; +} + +static void parse_list(struct list *list, char *s) +{ + list_erase(list); + + while (s && *s) { + char *sep; + + sep = strchr(s, ','); + if (sep) + *sep = '\0'; + + list_append(list, str_dup(s)); + + if (sep) + s = sep + 1; + else + break; + } +} + +int _map_log_level(char *conf_log_level) +{ + size_t i = 1; + + while (i < sizeof(log_level_map)/sizeof(char *)) { + if (strcasecmp(log_level_map[i], conf_log_level) == 0) + return i; + i++; + } + return 0; +} + +void _set_log_level(char *s) +{ + int log_level; + + log_level = _map_log_level(s); + if (log_level) + conf.log_level = log_level; + else if (sscanf(s, "%d", &log_level) == 1 && + log_level >= LOG_LEVEL_QUIET && + log_level <= LOG_LEVEL_ALL) + conf.log_level = log_level; + else + log_warning("Log level given in config file (%s) is incorrect! Using default log level: %s", + s, log_level_map[conf.log_level]); +} + +static int parse_next(FILE *fd) +{ + char buf[BUFSIZ]; + char *s; + + /* read the next non-blank non-comment line */ + do { + if (fgets(buf, sizeof(buf), fd) == NULL) + return feof(fd) ? 0 : -1; + s = strchr(buf, '\n'); + if (!s) { + /* Missing final newline? Otherwise extremely */ + /* long line - assume file was corrupted */ + if (feof(fd)) + s = strchr(buf, '\0'); + else { + fprintf(stderr, "config file: missing newline at line '%s'.", + buf); + return -1; + } + } + *s = '\0'; + if (--s >= buf && *s == '\r') + *s = '\0'; + + s = buf; + while (*s == ' ' || *s == '\t') /* skip space */ + s++; + + } while (*s == '\0' || *s == '#'); + + if (!strncmp(s, "INTERVAL=", 9)) { + s += 9; + if (*s) { + if (sscanf(s, "%d", &conf.scan_interval) != 1 || + conf.scan_interval < LEDMON_MIN_SLEEP_INTERVAL) + conf.scan_interval = LEDMON_MIN_SLEEP_INTERVAL; + } + } else if (!strncmp(s, "LOG_LEVEL=", 10)) { + s += 10; + _set_log_level(s); + } else if (!strncmp(s, "LOG_PATH=", 9)) { + s += 9; + if (*s) + set_log_path(s); + } else if (!strncmp(s, "BLINK_ON_MIGR=", 14)) { + s += 14; + conf.blink_on_migration = parse_bool(s); + if (conf.blink_on_migration < 0) + return -1; + } else if (!strncmp(s, "BLINK_ON_INIT=", 14)) { + s += 14; + conf.blink_on_init = parse_bool(s); + if (conf.blink_on_init < 0) + return -1; + } else if (!strncmp(s, "REBUILD_BLINK_ON_ALL=", 21)) { + s += 21; + conf.rebuild_blink_on_all = parse_bool(s); + if (conf.rebuild_blink_on_all < 0) + return -1; + } else if (!strncmp(s, "RAID_MEMBERS_ONLY=", 18)) { + s += 18; + conf.raid_members_only = parse_bool(s); + if (conf.raid_members_only < 0) + return -1; + } else if (!strncmp(s, "WHITELIST=", 10)) { + s += 10; + if (*s) + parse_list(&conf.cntrls_whitelist, s); + } else if (!strncmp(s, "BLACKLIST=", 10)) { + s += 10; + if (*s) + parse_list(&conf.cntrls_blacklist, s); + } else { + fprintf(stderr, "config file: unknown option '%s'.\n", s); + return -1; + } + return 0; +} + +void ledmon_free_config(void) +{ + list_erase(&conf.cntrls_blacklist); + list_erase(&conf.cntrls_whitelist); + + if (conf.log_path) + free(conf.log_path); +} + +/* return real config data or built-in default */ +int ledmon_read_config(const char *filename) +{ + FILE *f; + + if (!filename || (filename && access(filename, F_OK) < 0)) { + if (filename) + fprintf(stdout, "%s: does not exist, using global config file\n", + filename); + filename = LEDMON_DEF_CONF_FILE; + } + + f = fopen(filename, "re"); + if (!f) { + fprintf(stdout, "%s: does not exist, using built-in defaults\n", + filename); + } else { + while (!feof(f)) { + if (parse_next(f)) { + fprintf(stderr, "%s: parse error\n", filename); + ledmon_free_config(); + fclose(f); + return STATUS_CONFIG_FILE_ERROR; + } + } + fclose(f); + } + + if (!list_is_empty(&conf.cntrls_whitelist) && + !list_is_empty(&conf.cntrls_blacklist)) + fprintf(stdout, "Both whitelist and blacklist are specified - ignoring blacklist."); + + return STATUS_SUCCESS; +} + +static char *conf_list_to_str(struct list *list) +{ + char buf[BUFSIZ]; + char *elem; + + memset(buf, 0, sizeof(buf)); + list_for_each(list, elem) { + if (elem) { + int curr = strlen(buf); + + snprintf(buf + curr, sizeof(buf) - curr, "%s,", elem); + } + } + + return str_dup(buf); +} + +int ledmon_write_shared_conf(void) +{ + char buf[BUFSIZ]; + char *whitelist = NULL; + char *blacklist = NULL; + void *shared_mem_ptr; + int fd = shm_open(LEDMON_SHARE_MEM_FILE, O_RDWR | O_CREAT, 0644); + + if (fd == -1) + return STATUS_FILE_OPEN_ERROR; + + if (ftruncate(fd, sizeof(buf)) != 0) { + close(fd); + return STATUS_FILE_WRITE_ERROR; + } + + shared_mem_ptr = mmap(NULL, sizeof(buf), PROT_WRITE, MAP_SHARED, fd, 0); + if (shared_mem_ptr == MAP_FAILED) { + close(fd); + return STATUS_FILE_WRITE_ERROR; + } + + snprintf(buf, sizeof(buf), + "BLINK_ON_INIT=%d\n", conf.blink_on_init); + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + "BLINK_ON_MIGR=%d\n", conf.blink_on_migration); + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + "LOG_LEVEL=%u\n", conf.log_level); + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + "LOG_PATH=%s\n", conf.log_path); + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + "RAID_MEMBERS_ONLY=%d\n", conf.raid_members_only); + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + "REBUILD_BLINK_ON_ALL=%d\n", conf.rebuild_blink_on_all); + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + "INTERVAL=%d\n", conf.scan_interval); + whitelist = conf_list_to_str(&conf.cntrls_whitelist); + if (whitelist) { + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + "WHITELIST=%s\n", whitelist); + free(whitelist); + } + blacklist = conf_list_to_str(&conf.cntrls_blacklist); + if (blacklist) { + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + "BLACKLIST=%s\n", blacklist); + free(blacklist); + } + + memcpy(shared_mem_ptr, buf, strlen(buf)); + munmap(shared_mem_ptr, strlen(buf)); + close(fd); + + return STATUS_SUCCESS; +} + +int ledmon_remove_shared_conf(void) +{ + return shm_unlink(LEDMON_SHARE_MEM_FILE); +} + +#ifdef _TEST_CONFIG +/* + * usage: ledmon_conf_test [] + */ + +int main(int argc, char *argv[]) +{ + char *filename = NULL; + char *s; + + if (argc == 2) + filename = argv[1]; + + if (ledmon_read_config(filename) != STATUS_SUCCESS) + return EXIT_FAILURE; + + printf("INTERVAL: %d\n", conf.scan_interval); + printf("LOG_LEVEL: %d\n", conf.log_level); + printf("LOG_PATH: %s\n", conf.log_path); + printf("BLINK_ON_MIGR: %d\n", conf.blink_on_migration); + printf("BLINK_ON_INIT: %d\n", conf.blink_on_init); + printf("REBUILD_BLINK_ON_ALL: %d\n", conf.rebuild_blink_on_all); + printf("RAID_MEMBERS_ONLY: %d\n", conf.raid_members_only); + + if (list_is_empty(&conf.cntrls_whitelist)) + printf("WHITELIST: NONE\n"); + else { + printf("WHITELIST: "); + list_for_each(&conf.cntrls_whitelist, s) + printf("%s, ", s); + printf("\n"); + } + + if (list_is_empty(&conf.cntrls_blacklist)) + printf("BLACKLIST: NONE\n"); + else { + printf("BLACKLIST: "); + list_for_each(&conf.cntrls_blacklist, s) + printf("%s, ", s); + printf("\n"); + } + + ledmon_free_config(); + return EXIT_SUCCESS; +} +#endif diff --git a/src/config_file.h b/src/config_file.h new file mode 100644 index 0000000..55a80be --- /dev/null +++ b/src/config_file.h @@ -0,0 +1,58 @@ +/* + * Intel(R) Enclosure LED Utilities + * + * Copyright (C) 2017-2019 Intel Corporation. + * Copyright (C) 2009 Karel Zak + * + * SPDX-License-Identifier: GPL-2.0 + * + * Contains code from util-linux/libblkid/src/config.c + * originally released under LGPL. +*/ + +#ifndef SRC_CONFIG_FILE_H_ +#define SRC_CONFIG_FILE_H_ + +#include "list.h" + +#define LEDMON_SHARE_MEM_FILE "/ledmon.conf" +#define LEDMON_DEF_CONF_FILE "/etc/ledmon.conf" +#define LEDMON_DEF_LOG_FILE "/var/log/ledmon.log" +#define LEDCTL_DEF_LOG_FILE "/var/log/ledctl.log" +#define LEDMON_DEF_SLEEP_INTERVAL 10 +#define LEDMON_MIN_SLEEP_INTERVAL 5 + +enum log_level_enum { + LOG_LEVEL_UNDEF = 0, + LOG_LEVEL_QUIET, + LOG_LEVEL_ERROR, + LOG_LEVEL_WARNING, + LOG_LEVEL_INFO, + LOG_LEVEL_DEBUG, + LOG_LEVEL_ALL, +}; + +struct ledmon_conf { + /* internal ledmon functions */ + char *log_path; + enum log_level_enum log_level; + int scan_interval; + + /* customizable leds behaviour */ + int blink_on_migration; + int blink_on_init; + int rebuild_blink_on_all; + int raid_members_only; + + /* whitelist and blacklist of controllers for blinking */ + struct list cntrls_whitelist; + struct list cntrls_blacklist; +}; + +extern struct ledmon_conf conf; + +int ledmon_read_config(const char *filename); +int ledmon_write_shared_conf(void); +int ledmon_remove_shared_conf(void); + +#endif /* SRC_CONFIG_FILE_H_ */ diff --git a/src/dellssd.c b/src/dellssd.c new file mode 100644 index 0000000..5a7ac5f --- /dev/null +++ b/src/dellssd.c @@ -0,0 +1,245 @@ +/* + * Dell Backplane LED control + * Copyright (C) 2011, Dell Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#if _HAVE_DMALLOC_H +#include +#endif + +#include "ahci.h" +#include "cntrl.h" +#include "config.h" +#include "dellssd.h" +#include "ibpi.h" +#include "list.h" +#include "raid.h" +#include "scsi.h" +#include "slave.h" +#include "smp.h" +#include "status.h" +#include "sysfs.h" +#include "utils.h" +#include "ipmi.h" + +#define BP_PRESENT (1L << 0) +#define BP_ONLINE (1L << 1) +#define BP_HOTSPARE (1L << 2) +#define BP_IDENTIFY (1L << 3) +#define BP_REBUILDING (1L << 4) +#define BP_FAULT (1L << 5) +#define BP_PREDICT (1L << 6) +#define BP_CRITICALARRAY (1L << 9) +#define BP_FAILEDARRAY (1L << 10) + +static const unsigned int ibpi2ssd[] = { + [IBPI_PATTERN_UNKNOWN] = BP_ONLINE, + [IBPI_PATTERN_ONESHOT_NORMAL] = BP_ONLINE, + [IBPI_PATTERN_NORMAL] = BP_ONLINE, + [IBPI_PATTERN_DEGRADED] = BP_CRITICALARRAY|BP_ONLINE, + [IBPI_PATTERN_REBUILD] = BP_REBUILDING|BP_ONLINE, + [IBPI_PATTERN_FAILED_ARRAY] = BP_FAILEDARRAY|BP_ONLINE, + [IBPI_PATTERN_HOTSPARE] = BP_HOTSPARE|BP_ONLINE, + [IBPI_PATTERN_PFA] = BP_PREDICT|BP_ONLINE, + [IBPI_PATTERN_FAILED_DRIVE] = BP_FAULT|BP_ONLINE, + [IBPI_PATTERN_LOCATE] = BP_IDENTIFY|BP_ONLINE, + [IBPI_PATTERN_LOCATE_OFF] = BP_ONLINE +}; + +#define DELL_OEM_NETFN 0x30 + +#define DELL_OEM_STORAGE_CMD 0xD5 +#define DELL_OEM_STORAGE_GETDRVMAP_12G 0x07 +#define DELL_OEM_STORAGE_SETDRVSTATUS_12G 0x04 +#define DELL_OEM_STORAGE_GETDRVMAP_13G 0x17 +#define DELL_OEM_STORAGE_SETDRVSTATUS_13G 0x14 +#define DELL_OEM_STORAGE_GETDRVMAP_14G 0x37 +#define DELL_OEM_STORAGE_SETDRVSTATUS_14G 0x34 + +#define APP_NETFN 0x06 +#define APP_GET_SYSTEM_INFO 0x59 +#define DELL_GET_IDRAC_INFO 0xDD + +enum { + DELL_12G_MONOLITHIC = 0x10, + DELL_12G_MODULAR = 0x11, + DELL_13G_MONOLITHIC = 0x20, + DELL_13G_MODULAR = 0x21, + DELL_14G_MONOLITHIC = 0x30, + DELL_14G_MODULAR = 0x31, +}; + +int get_dell_server_type() +{ + static int gen; + uint8_t data[4], rdata[20]; + int rc, rlen; + + /* Don't requery if we already know have ID */ + if (gen) + return gen; + + /* Get Dell Generation */ + memset(data, 0, sizeof(data)); + memset(rdata, 0, sizeof(rdata)); + data[0] = 0x00; + data[1] = DELL_GET_IDRAC_INFO; + data[2] = 0x02; + data[3] = 0x00; + rc = ipmicmd(BMC_SA, 0, APP_NETFN, APP_GET_SYSTEM_INFO, 4, data, + 20, &rlen, rdata); + if (rc) { + log_debug("Unable to issue IPMI command GetSystemInfo\n"); + return 0; + } + switch (rdata[10]) { + case DELL_12G_MONOLITHIC: + case DELL_12G_MODULAR: + case DELL_13G_MONOLITHIC: + case DELL_13G_MODULAR: + case DELL_14G_MONOLITHIC: + case DELL_14G_MODULAR: + gen = rdata[10]; + return gen; + default: + log_debug("Unable to determine Dell Server type\n"); + break; + } + return 0; +} + +static int ipmi_setled(int b, int d, int f, int state) +{ + uint8_t data[20], rdata[20]; + int rc, rlen, bay = 0xFF, slot = 0xFF, devfn, gen = 0; + + /* Check if this is a supported Dell server */ + gen = get_dell_server_type(); + if (!gen) + return 0; + devfn = (((d & 0x1F) << 3) | (f & 0x7)); + + /* Get mapping of BDF to bay:slot */ + memset(data, 0, sizeof(data)); + memset(rdata, 0, sizeof(rdata)); + data[0] = 0x01; /* get */ + data[2] = 0x06; /* length lsb */ + data[3] = 0x00; /* length msb */ + data[4] = 0x00; /* offset lsb */ + data[5] = 0x00; /* offset msb */ + data[6] = b; /* bus */ + data[7] = devfn; /* devfn */ + switch (gen) { + case DELL_12G_MONOLITHIC: + case DELL_12G_MODULAR: + data[1] = DELL_OEM_STORAGE_GETDRVMAP_12G; + break; + case DELL_13G_MONOLITHIC: + case DELL_13G_MODULAR: + data[1] = DELL_OEM_STORAGE_GETDRVMAP_13G; + break; + case DELL_14G_MONOLITHIC: + case DELL_14G_MODULAR: + data[1] = DELL_OEM_STORAGE_GETDRVMAP_14G; + break; + } + rc = ipmicmd(BMC_SA, 0, DELL_OEM_NETFN, DELL_OEM_STORAGE_CMD, 8, data, + 20, &rlen, rdata); + if (!rc) { + bay = rdata[7]; + slot = rdata[8]; + } + if (bay == 0xFF || slot == 0xFF) { + log_error("Unable to determine bay/slot for device %.2x:%.2x.%x\n", + b, d, f); + return 0; + } + + /* Set Bay:Slot to Mask */ + memset(data, 0, sizeof(data)); + memset(rdata, 0, sizeof(rdata)); + data[0] = 0x00; /* set */ + data[2] = 0x0e; /* length lsb */ + data[3] = 0x00; /* length msb */ + data[4] = 0x00; /* offset lsb */ + data[5] = 0x00; /* offset msb */ + data[6] = 0x0e; /* length lsb */ + data[7] = 0x00; /* length msb */ + data[8] = bay; /* bayid */ + data[9] = slot; /* slotid */ + data[10] = state & 0xff; /* state LSB */ + data[11] = state >> 8; /* state MSB */ + switch (gen) { + case DELL_12G_MONOLITHIC: + case DELL_12G_MODULAR: + data[1] = DELL_OEM_STORAGE_SETDRVSTATUS_12G; + break; + case DELL_13G_MONOLITHIC: + case DELL_13G_MODULAR: + data[1] = DELL_OEM_STORAGE_SETDRVSTATUS_13G; + break; + case DELL_14G_MONOLITHIC: + case DELL_14G_MODULAR: + data[1] = DELL_OEM_STORAGE_SETDRVSTATUS_14G; + break; + } + rc = ipmicmd(BMC_SA, 0, DELL_OEM_NETFN, DELL_OEM_STORAGE_CMD, 20, data, + 20, &rlen, rdata); + if (rc) { + log_error("Unable to issue SetDriveState for %.2x:%.2x.%x\n", + b,d,f); + } + return 0; +} + +char *dellssd_get_path(const char *cntrl_path) +{ + return str_dup(cntrl_path); +} + +int dellssd_write(struct block_device *device, enum ibpi_pattern ibpi) +{ + unsigned int mask, bus, dev, fun; + char *t; + + /* write only if state has changed */ + if (ibpi == device->ibpi_prev) + return 1; + + if ((ibpi < IBPI_PATTERN_NORMAL) || (ibpi > IBPI_PATTERN_LOCATE_OFF)) + __set_errno_and_return(ERANGE); + mask = ibpi2ssd[ibpi]; + t = strrchr(device->cntrl_path, '/'); + if (t != NULL) { + /* Extract PCI bus:device.function */ + if (sscanf(t + 1, "%*x:%x:%x.%x", &bus, &dev, &fun) == 3) + ipmi_setled(bus, dev, fun, mask); + } + return 0; +} diff --git a/src/dellssd.h b/src/dellssd.h new file mode 100644 index 0000000..0315a22 --- /dev/null +++ b/src/dellssd.h @@ -0,0 +1,24 @@ +/* + * Dell Backplane LED control + * Copyright (C) 2011, Dell Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#include "block.h" + +int dellssd_write(struct block_device *device, enum ibpi_pattern ibpi); +char *dellssd_get_path(const char *cntrl_path); +int get_dell_server_type(void); diff --git a/src/enclosure.c b/src/enclosure.c new file mode 100644 index 0000000..25b7ec2 --- /dev/null +++ b/src/enclosure.c @@ -0,0 +1,136 @@ +/* + * Intel(R) Enclosure LED Utilities + * Copyright (C) 2009-2019 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#include +#include +#include +#include +#include +#include + +#if _HAVE_DMALLOC_H +#include +#endif + +#include "config.h" +#include "enclosure.h" +#include "utils.h" + +/** + * @brief Gets SAS address of an enclosure device. + * + * This is internal function of enclosure module. The function reads a + * SAS address of an enclosure from sysfs attribute. + * + * @param[in] path Path to enclosure device in sysfs tree. + * + * @return SAS address of an enclosure if successful, otherwise 0. + */ +#define SAS_DEVICE "/sas_device" +static uint64_t _get_sas_address(const char *path) +{ + char *tmp = str_dup(path); + char buf[PATH_MAX]; + char *p, *s; + + p = strstr(tmp, "/expander"); + if (p == NULL) + goto out; + s = strchr(p + 1, PATH_DELIM); + if (s == NULL) + goto out; + *s = '\0'; + snprintf(buf, sizeof(buf), "%s%s%s", tmp, SAS_DEVICE, p); + free(tmp); + return get_uint64(buf, 0, "sas_address"); + +out: + free(tmp); + return 0; +} + +#define SCSI_GEN "device/scsi_generic" + +static char *_get_dev_sg(const char *encl_path) +{ + char *ret = NULL; + DIR *d; + struct dirent *de; + size_t sg_path_size = strlen(encl_path) + strlen(SCSI_GEN) + 2; + char *sg_path = malloc(sg_path_size); + + if (!sg_path) + return NULL; + + snprintf(sg_path, sg_path_size, "%s/%s", encl_path, SCSI_GEN); + + /* /sys/class/enclosure/X/device/scsi_generic path is expected. */ + + d = opendir(sg_path); + free(sg_path); + if (!d) + return NULL; + while ((de = readdir(d))) { + if ((strcmp(de->d_name, ".") == 0) || + (strcmp(de->d_name, "..") == 0)) + continue; + break; + } + if (de) { + size_t size = strlen("/dev/") + strlen(de->d_name) + 1; + ret = malloc(size); + if (ret) + snprintf(ret, size, "/dev/%s", de->d_name); + } + closedir(d); + return ret; +} + +/* + * Allocates memory for enclosure device structure and initializes fields of + * the structure. + */ +struct enclosure_device *enclosure_device_init(const char *path) +{ + char temp[PATH_MAX]; + struct enclosure_device *result = NULL; + + if (realpath(path, temp)) { + result = calloc(1, sizeof(struct enclosure_device)); + if (result == NULL) + return NULL; + result->sysfs_path = str_dup(temp); + result->sas_address = _get_sas_address(temp); + result->dev_path = _get_dev_sg(temp); + } + return result; +} + +/* + * The function returns memory allocated for fields of enclosure structure to + * the system. + */ +void enclosure_device_fini(struct enclosure_device *enclosure) +{ + if (enclosure) { + free(enclosure->sysfs_path); + free(enclosure->dev_path); + free(enclosure); + } +} diff --git a/src/enclosure.h b/src/enclosure.h new file mode 100644 index 0000000..5dcdb57 --- /dev/null +++ b/src/enclosure.h @@ -0,0 +1,81 @@ +/* + * Intel(R) Enclosure LED Utilities + * Copyright (C) 2009-2018 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#ifndef _ENCLOSURE_H_INCLUDED_ +#define _ENCLOSURE_H_INCLUDED_ + +#include + +#include "ses.h" + +/** + * @brief Enclosure device structure. + * + * This structure describes an enclosure device connected to one of + * SAS controllers existing in the system. + */ +struct enclosure_device { + /** + * Path to an enclosure device in sysfs tree. This is controller base + * canonical path. + */ + char *sysfs_path; + + /** + * SAS address as identifier of an enclosure. + */ + uint64_t sas_address; + + /** + * Path to enclosure's sg device. + */ + char *dev_path; + + struct ses_pages *ses_pages; +}; + +/** + * @brief Allocates memory for an enclosure device structure. + * + * This function allocates memory for a new structure describing an enclosure + * device. It reads the sysfs entries and populates structure fields. + * The function uses libsas abstraction layer to extract required information. + * + * @param[in] path Path to an enclosure device in sysfs tree. + * The path begins with "/sys/class/enclosure/". + * + * @return Pointer to enclosure device structure if successful, otherwise the + * function returns NULL pointer. The NULL pointer means either the + * specified path is invalid or there's not enough memory in the system + * to allocated new structure. + */ +struct enclosure_device *enclosure_device_init(const char *path); + +/** + * @brief Releases an enclosure device structure. + * + * This function releases memory allocated for enclosure device structure. + * + * @param[in] device Pointer to enclosure device structure. + * + * @return The function does not return a value. + */ +void enclosure_device_fini(struct enclosure_device *enclosure); + +#endif /* _ENCLOSURE_H_INCLUDED_ */ diff --git a/src/ibpi.h b/src/ibpi.h new file mode 100644 index 0000000..109f112 --- /dev/null +++ b/src/ibpi.h @@ -0,0 +1,101 @@ +/* + * Intel(R) Enclosure LED Utilities + * Copyright (C) 2009-2018 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#ifndef _IBPI_H_INCLUDED_ +#define _IBPI_H_INCLUDED_ + +/** + * @brief IBPI pattern identifies. + * + * The IBPI specification lists the following pattern names: + * + * - NORMAL - either drive is present or missing, activity LED does not + * matter. The rest of the LEDs are off. + * - FAIL - a block device has failed or is missing. Failure LED is + * active and the behavior is depended on implementation + * of enclosure management processor. + * - REBUILD - this means a RAID device is recovering or rebuilding + * its data. Depending on implementation of enclosure + * management processor appropriate LED is blinking or solid. + * - ICA - In a Critical Array, this means a RAID device is degraded and + * there's no spare device available. + * - IFA - In a Failed Array, this means a RAID device is damaged and + * cannot be recovered or rebuild. + * - PFA - Predict Failure Analysis state means that a block device will + * fail soon, so it must be replaced with working one. + * - LOCATE - turns Locate LED on to identify a block device or slot. + * + * Additionally the following patterns has been introduced, just for the purpose + * of LED control utility. + * + * - UNKNOWN - unknown IBPI pattern and it means do not control LEDs for + * a device it is set (no LED management). + * - ONESHOT_NORMAL - this state means that ledmon just started and it does not + * know anything about existing patterns set, so it will off all + * the LEDs just in case of any problem in the future. The state + * is set, when a RAID device disappears, too. Oneshot means + * as soon application applies the state it will change + * to UNKNOWN. + * - ADDED this state means that device previously known to ledmon is + * restored. This state will be changed to ONESHOT_NORMAL. + * - REMOVED this state means that device was removed from system. It + * will be changed to ADDED after restoring device to system. + */ +enum ibpi_pattern { + IBPI_PATTERN_UNKNOWN = 0, + IBPI_PATTERN_NONE, /* used only to initialize ibpi_prev */ + IBPI_PATTERN_NORMAL, + IBPI_PATTERN_ONESHOT_NORMAL, + IBPI_PATTERN_DEGRADED, + IBPI_PATTERN_HOTSPARE, + IBPI_PATTERN_REBUILD, + IBPI_PATTERN_FAILED_ARRAY, + IBPI_PATTERN_PFA, + IBPI_PATTERN_FAILED_DRIVE, + IBPI_PATTERN_LOCATE, + IBPI_PATTERN_LOCATE_OFF, + IBPI_PATTERN_ADDED, + IBPI_PATTERN_REMOVED, + /* Below are SES-2 codes. Note that by default most IBPI messages are + * translated into SES when needed but SES codes can be added also. */ + SES_REQ_ABORT, + SES_REQ_REBUILD, + SES_REQ_IFA, + SES_REQ_ICA, + SES_REQ_CONS_CHECK, + SES_REQ_HOSTSPARE, + SES_REQ_RSVD_DEV, + SES_REQ_OK, + SES_REQ_IDENT, + SES_REQ_RM, + SES_REQ_INS, + SES_REQ_MISSING, + SES_REQ_DNR, + SES_REQ_ACTIVE, + SES_REQ_EN_BB, + SES_REQ_EN_BA, + SES_REQ_DEV_OFF, + SES_REQ_FAULT, + SES_REQ_PRDFAIL, + ibpi_pattern_count, +}; + +extern const char *ibpi_str[ibpi_pattern_count]; + +#endif /* _IBPI_H_INCLUDED_ */ diff --git a/src/ipmi.c b/src/ipmi.c new file mode 100644 index 0000000..065247f --- /dev/null +++ b/src/ipmi.c @@ -0,0 +1,135 @@ +/* + * Generic IPMI Interface + * Copyright (C) 2011, Dell Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#if _HAVE_DMALLOC_H +#include +#endif + +#include "utils.h" +#include "ipmi.h" + +static int ipmi_open(void) +{ + int fd; + + fd = open("/dev/ipmi0", O_RDWR); + if (fd >= 0) + return fd; + fd = open("/dev/ipmidev/0", O_RDWR); + if (fd >= 0) + return fd; + fd = open("/dev/ipmidev0", O_RDWR); + if (fd >= 0) + return fd; + fd = open("/dev/bmc", O_RDWR); + if (fd >= 0) + return fd; + return -1; +} + +int ipmicmd(int sa, int lun, int netfn, int cmd, int datalen, void *data, + int resplen, int *rlen, void *resp) +{ + static int msgid; + struct ipmi_system_interface_addr saddr; + struct ipmi_ipmb_addr iaddr; + struct ipmi_addr raddr; + struct ipmi_req req; + struct ipmi_recv rcv; + fd_set rfd; + int fd, rc; + uint8_t tresp[resplen + 1]; + + fd = ipmi_open(); + if (fd < 0) + return -1; + + memset(&req, 0, sizeof(req)); + memset(&rcv, 0, sizeof(rcv)); + if (sa == BMC_SA) { + memset(&saddr, 0, sizeof(saddr)); + saddr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE; + saddr.channel = IPMI_BMC_CHANNEL; + saddr.lun = 0; + req.addr = (void *)&saddr; + req.addr_len = sizeof(saddr); + } else { + memset(&iaddr, 0, sizeof(iaddr)); + iaddr.addr_type = IPMI_IPMB_ADDR_TYPE; + iaddr.channel = 0; + iaddr.slave_addr = sa; + iaddr.lun = lun; + req.addr = (void *)&iaddr; + req.addr_len = sizeof(iaddr); + } + + /* Issue command */ + req.msgid = ++msgid; + req.msg.netfn = netfn; + req.msg.cmd = cmd; + req.msg.data_len = datalen; + req.msg.data = data; + rc = ioctl(fd, IPMICTL_SEND_COMMAND, (void *)&req); + if (rc != 0) { + perror("send"); + goto end; + } + + /* Wait for Response */ + FD_ZERO(&rfd); + FD_SET(fd, &rfd); + rc = select(fd + 1, &rfd, NULL, NULL, NULL); + if (rc < 0) { + perror("select"); + goto end; + } + + /* Get response */ + rcv.msg.data = tresp; + rcv.msg.data_len = resplen + 1; + rcv.addr = (void *)&raddr; + rcv.addr_len = sizeof(raddr); + rc = ioctl(fd, IPMICTL_RECEIVE_MSG_TRUNC, (void *)&rcv); + if (rc != 0 && errno == EMSGSIZE) + log_info("too short..\n"); + if (rc != 0 && errno != EMSGSIZE) { + log_info("%s\n", strerror(errno)); + goto end; + } + if (rcv.msg.data[0]) + log_info("IPMI Error: %.2x\n", rcv.msg.data[0]); + rc = 0; + *rlen = rcv.msg.data_len - 1; + memcpy(resp, rcv.msg.data + 1, *rlen); +end: + close(fd); + return rc; +} diff --git a/src/ipmi.h b/src/ipmi.h new file mode 100644 index 0000000..35d8f7e --- /dev/null +++ b/src/ipmi.h @@ -0,0 +1,22 @@ +/* + * Generic IPMI Interface + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#define BMC_SA 0x20 + +int ipmicmd(int sa, int lun, int netfn, int cmd, int datalen, void *data, + int resplen, int *rlen, void *resp); diff --git a/src/ledctl.c b/src/ledctl.c new file mode 100644 index 0000000..9d4790e --- /dev/null +++ b/src/ledctl.c @@ -0,0 +1,715 @@ +/* + * Intel(R) Enclosure LED Utilities + * Copyright (C) 2009-2019 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if _HAVE_DMALLOC_H +#include +#endif + +#include "ahci.h" +#include "block.h" +#include "cntrl.h" +#include "config.h" +#include "config_file.h" +#include "ibpi.h" +#include "list.h" +#include "scsi.h" +#include "status.h" +#include "sysfs.h" +#include "utils.h" +#include "version.h" + +/** + * @brief An IBPI state structure. + * + * This structure connects an IBPI pattern and block devices. It is used by + * _determine() function to figure the correct pattern out. + */ +struct ibpi_state { + enum ibpi_pattern ibpi; + struct list block_list; +}; + +/** + * @brief List of IBPI patterns. + * + * This is a list of IBPI patterns the user requested to be visualized. + * Each element on the list is struct ibpi_state type. There's only one + * instance of each IBPI pattern on the list (no duplicates). + */ +static struct list ibpi_list; + +/** + * @brief IBPI pattern names. + * + * This is internal array holding names of IBPI pattern. Logging routines use + * this entries to translate enumeration type values into the string. + */ +const char *ibpi_str[] = { + [IBPI_PATTERN_UNKNOWN] = "", + [IBPI_PATTERN_NORMAL] = "NORMAL", + [IBPI_PATTERN_ONESHOT_NORMAL] = "", + [IBPI_PATTERN_DEGRADED] = "ICA", + [IBPI_PATTERN_REBUILD] = "REBUILD", + [IBPI_PATTERN_FAILED_ARRAY] = "IFA", + [IBPI_PATTERN_HOTSPARE] = "HOTSPARE", + [IBPI_PATTERN_PFA] = "PFA", + [IBPI_PATTERN_FAILED_DRIVE] = "FAILURE", + [IBPI_PATTERN_LOCATE] = "LOCATE", + [IBPI_PATTERN_LOCATE_OFF] = "LOCATE_OFF", + [IBPI_PATTERN_ADDED] = "ADDED", + [IBPI_PATTERN_REMOVED] = "REMOVED" +}; + +/** + * Internal variable of ledctl utility. It is the pattern used to print out + * information about the version of ledctl utility. + */ +static char *ledctl_version = "Intel(R) Enclosure LED Control Application %d.%d %s\n" + "Copyright (C) 2009-2019 Intel Corporation.\n"; + +/** + * Internal variable of monitor service. It is used to help parse command line + * short options. + */ +static char *shortopt; + +struct option *longopt; + +static int possible_params[] = { + OPT_HELP, + OPT_LOG, + OPT_VERSION, + OPT_LIST_CTRL, + OPT_LISTED_ONLY, + OPT_ALL, + OPT_DEBUG, + OPT_ERROR, + OPT_INFO, + OPT_QUIET, + OPT_WARNING, + OPT_LOG_LEVEL, +}; + +static const int possible_params_size = sizeof(possible_params) + / sizeof(possible_params[0]); + +static int listed_only; + +static void ibpi_state_fini(struct ibpi_state *p) +{ + list_clear(&p->block_list); + free(p); +} + +/** + * @brief Finalizes LED control utility. + * + * This is internal function of ledctl utility. The function cleans up a memory + * allocated for the application and closes all opened handles. This function is + * design to be registered as on_exit() handler function. + * + * @param[in] status exit status of the ledctl application. + * @param[in] ignored function ignores this argument. + * + * @return The function does not return a value. + */ +static void _ledctl_fini(int status __attribute__ ((unused)), + void *ignore __attribute__ ((unused))) +{ + sysfs_reset(); + list_erase(&ibpi_list); + log_close(); +} + +/** + * @brief Displays the credits. + * + * This is internal function of ledctl utility. It prints out the name and + * version of the program. It displays the copyright notice and information + * about the author and license, too. + * + * @return The function does not return a value. + */ +static void _ledctl_version(void) +{ + printf(ledctl_version, VERSION_MAJOR, VERSION_MINOR, BUILD_LABEL); + printf("\nThis is free software; see the source for copying conditions." \ + " There is NO warranty;\nnot even for MERCHANTABILITY or FITNESS" \ + " FOR A PARTICULAR PURPOSE.\n\n"); +} + +/** + * @brief Displays the help. + * + * This is internal function of ledctl utility. The function prints the name + * and version of the program out. It displays the usage and available options + * and its arguments (if any). Each option is described. This is an extract + * from user manual page. + * + * @return The function does not return a value. + */ +static void _ledctl_help(void) +{ + printf(ledctl_version, VERSION_MAJOR, VERSION_MINOR, BUILD_LABEL); + printf("\nUsage: %s [OPTIONS] pattern=list_of_devices ...\n\n", + progname); + printf("Mandatory arguments for long options are mandatory for short options, too.\n\n"); + print_opt("--listed-only", "-x", + "Ledctl will change state only for given devices."); + print_opt("--list-controllers", "-L", + "Displays list of controllers detected by ledmon."); + print_opt("--log=PATH", "-l PATH", + "Use local log file instead /var/log/ledctl.log."); + print_opt("--help", "-h", "Displays this help text."); + print_opt("--version", "-v", + "Displays version and license information."); + print_opt("--log-level=VALUE", "-l VALUE", + "Allows user to set ledctl verbose level in logs."); + printf("\nPatterns:\n" + "\tCommon patterns are:\n" + "\t\tlocate, locate_off, normal, off, degraded, rebuild,\n" "" + "\t\tfailed_array, hotspare, pfa, failure, disk_failed\n" + "\tSES-2 only patterns:\n" + "\t\tses_abort, ses_rebuild, ses_ifa, ses_ica, ses_cons_check,\n" + "\t\tses_hotspare, ses_rsvd_dev, ses_ok, ses_ident, ses_rm,\n" + "\t\tses_insert, ses_missing, ses_dnr, ses_active, ses_prdfail,\n" + "\t\tses_enable_bb, ses_enable_ba, ses_devoff, ses_fault\n" + "\tAutomatic translation form IBPI into SES-2:\n" + "\t\tlocate=ses_ident, locate_off=~ses_ident,\n" + "\t\tnormal=ses_ok, off=ses_ok, degraded=ses_ica,\n" + "\t\trebuild=ses_rebuild, failed_array=ses_ifa,\n" + "\t\thotspare=ses_hotspare, pfa=ses_prdfail, failure=ses_fault,\n" + "\t\tdisk_failed=ses_fault\n"); + printf("Refer to ledctl(8) man page for more detailed description.\n"); + printf("Bugs should be reported at: " \ + "https://github.com/intel/ledmon/issues\n"); +} + +/** + * @brief Puts new IBPI state on the list. + * + * This is internal function of ledctl utility. The function creates a new entry + * of the list with IBPI patterns. Each IBPI state has a list of block devices + * attached. The function initializes this list and sets empty. + * + * @param[in] ibpi an IBPI pattern to add. + * + * @return Pointer to the created element if successful, otherwise function + * returns NULL. The NULL pointer means element allocation failed. + */ +static struct ibpi_state *_ibpi_state_init(enum ibpi_pattern ibpi) +{ + struct ibpi_state *state = malloc(sizeof(struct ibpi_state)); + + if (!state) + return NULL; + + list_init(&state->block_list, NULL); + state->ibpi = ibpi; + + list_append(&ibpi_list, state); + + return state; +} + +/** + * @brief Sets a state of block device. + * + * This is internal function of ledctl utility. The function sets + * an IBPI pattern for block devices. The function is design to be used + * as action parameter of list_for_each() function. + * + * @param[in] state pointer to structure holding the IBPI pattern + * identifier and list of block devices. + * + * @return The function does not return a value. + */ +static void _determine(struct ibpi_state *state) +{ + if (list_is_empty(&state->block_list) == 0) { + struct block_device *block; + + list_for_each(&state->block_list, block) { + if (block->ibpi < state->ibpi) + block->ibpi = state->ibpi; + } + } else { + log_warning + ("IBPI %s: missing block device(s)... pattern ignored.", + ibpi2str(state->ibpi)); + } +} + +/** + * @brief Determines a state of block devices. + * + * This is internal function of ledctl utility. The functions goes through list + * of IBPI states and calls _determine() function for each element. If the list + * is empty the function logs a warning message and does nothing. + * + * @param[in] ibpi_local_list pointer to list of IBPI states. + * + * @return STATUS_SUCCESS if successful, otherwise a valid status_t status code. + * The following status codes function returns: + * + * STATUS_LIST_EMPTY the specified list has no elements. + */ +static status_t _ibpi_state_determine(struct list *ibpi_local_list) +{ + if (list_is_empty(ibpi_local_list) == 0) { + struct ibpi_state *state; + + list_for_each(ibpi_local_list, state) + _determine(state); + return STATUS_SUCCESS; + } + log_error("missing operand(s)... run %s --help for details.", progname); + return STATUS_LIST_EMPTY; +} + +static struct ibpi_state *_ibpi_find(const struct list *ibpi_local_list, + enum ibpi_pattern ibpi) +{ + struct ibpi_state *state; + + list_for_each(ibpi_local_list, state) { + if (state->ibpi == ibpi) + return state; + } + return NULL; +} + +/** + * @brief Gets a pointer to IBPI state structure. + * + * This is internal function of ledctl utility. The function retrieves an entry + * to an IBPI state structure from ibpi_list list. If such an entry does not + * exist the memory is allocated and entry is added to the list. + * + * @param[in] name a name of IBPI pattern i.e. taken from command + * line interface. It might be 'locate', 'normal', + * 'locate_off', 'off', 'ica', 'degraded', 'rebuild', + * 'ifa', 'failed_array', 'hotspare', + * 'pfa', 'failure' or 'disk_failed' string. + * + * @return Pointer to IBPI state structure if successful, otherwise the function + * returns NULL. The NULL pointer means either the invalid status name + * has been given or there's not enough memory available in the system + * to allocate the structure. + */ +static struct ibpi_state *_ibpi_state_get(const char *name) +{ + struct ibpi_state *state = NULL; + enum ibpi_pattern ibpi; + + if (strcmp(name, "locate") == 0) { + ibpi = IBPI_PATTERN_LOCATE; + } else if (strcmp(name, "locate_off") == 0) { + ibpi = IBPI_PATTERN_LOCATE_OFF; + } else if (strcmp(name, "normal") == 0) { + ibpi = IBPI_PATTERN_NORMAL; + } else if (strcmp(name, "off") == 0) { + ibpi = IBPI_PATTERN_NORMAL; + } else if ((strcmp(name, "ica") == 0) || + (strcmp(name, "degraded") == 0)) { + ibpi = IBPI_PATTERN_DEGRADED; + } else if (strcmp(name, "rebuild") == 0) { + ibpi = IBPI_PATTERN_REBUILD; + } else if ((strcmp(name, "ifa") == 0) || + (strcmp(name, "failed_array") == 0)) { + ibpi = IBPI_PATTERN_FAILED_ARRAY; + } else if (strcmp(name, "hotspare") == 0) { + ibpi = IBPI_PATTERN_HOTSPARE; + } else if (strcmp(name, "pfa") == 0) { + ibpi = IBPI_PATTERN_PFA; + } else if ((strcmp(name, "failure") == 0) || + (strcmp(name, "disk_failed") == 0)) { + ibpi = IBPI_PATTERN_FAILED_DRIVE; + } else if (strcmp(name, "ses_abort") == 0) { + ibpi = SES_REQ_ABORT; + } else if (strcmp(name, "ses_rebuild") == 0) { + ibpi = SES_REQ_REBUILD; + } else if (strcmp(name, "ses_ifa") == 0) { + ibpi = SES_REQ_IFA; + } else if (strcmp(name, "ses_ica") == 0) { + ibpi = SES_REQ_ICA; + } else if (strcmp(name, "ses_cons_check") == 0) { + ibpi = SES_REQ_CONS_CHECK; + } else if (strcmp(name, "ses_hotspare") == 0) { + ibpi = SES_REQ_HOSTSPARE; + } else if (strcmp(name, "ses_rsvd_dev") == 0) { + ibpi = SES_REQ_RSVD_DEV; + } else if (strcmp(name, "ses_ok") == 0) { + ibpi = SES_REQ_OK; + } else if (strcmp(name, "ses_ident") == 0) { + ibpi = SES_REQ_IDENT; + } else if (strcmp(name, "ses_rm") == 0) { + ibpi = SES_REQ_RM; + } else if (strcmp(name, "ses_insert") == 0) { + ibpi = SES_REQ_INS; + } else if (strcmp(name, "ses_missing") == 0) { + ibpi = SES_REQ_MISSING; + } else if (strcmp(name, "ses_dnr") == 0) { + ibpi = SES_REQ_DNR; + } else if (strcmp(name, "ses_active") == 0) { + ibpi = SES_REQ_ACTIVE; + } else if (strcmp(name, "ses_enable_bb") == 0) { + ibpi = SES_REQ_EN_BB; + } else if (strcmp(name, "ses_enable_ba") == 0) { + ibpi = SES_REQ_EN_BA; + } else if (strcmp(name, "ses_devoff") == 0) { + ibpi = SES_REQ_DEV_OFF; + } else if (strcmp(name, "ses_fault") == 0) { + ibpi = SES_REQ_FAULT; + } else if (strcmp(name, "ses_prdfail") == 0) { + ibpi = SES_REQ_PRDFAIL; + } else { + return NULL; + } + state = _ibpi_find(&ibpi_list, ibpi); + if (state == NULL) + state = _ibpi_state_init(ibpi); + return state; +} + +static struct block_device *_block_device_search(const struct list *block_list, + const char *path) +{ + struct block_device *block; + + list_for_each(block_list, block) { + if (strcmp(block->sysfs_path, path) == 0) + return block; + } + return NULL; +} + +/** + * @brief Adds a block device to a block list. + * + * This is internal function of ledctl utility. Each IBPI state has list of + * block devices attached to. The function puts a pointer to a block device + * on that list. First the function determines the canonical version of the + * given path and checks if it is correct. If the path to /dev directory is + * given the function finds out the correct entry in sysfs tree. + * + * @param[in] state pointer to IBPI state structure the block + * device will be added to. + * @param[in] block pointer to block device structure. + * + * @return The function does not return a value. + */ +static status_t _ibpi_state_add_block(struct ibpi_state *state, char *name) +{ + struct stat st; + char temp[PATH_MAX], path[PATH_MAX]; + struct block_device *blk1, *blk2; + + if ((realpath(name, temp) == NULL) && (errno != ENOTDIR)) + return STATUS_INVALID_PATH; + if (strstr(temp, "/dev/") != NULL) { + if (stat(temp, &st) < 0) + return STATUS_STAT_ERROR; + sprintf(temp, "/sys/dev/block/%u:%u", major(st.st_rdev), + minor(st.st_rdev)); + if ((realpath(temp, path) == NULL) && (errno != ENOTDIR)) + return STATUS_INVALID_PATH; + } else { + str_cpy(path, temp, PATH_MAX); + } + blk1 = _block_device_search(sysfs_get_block_devices(), path); + if (blk1 == NULL) { + log_error("%s: device not supported", name); + return STATUS_NOT_SUPPORTED; + } + blk2 = _block_device_search(&state->block_list, path); + if (blk2 == NULL) + list_append(&state->block_list, blk1); + else + log_info("%s: %s: device already on the list.", + ibpi2str(state->ibpi), path); + return STATUS_SUCCESS; +} + +/** + * @brief Command line parser - operands. + * + * This is internal function of ledctl utility. The function parses operands of + * ledctl application. The operands section contains the pattern name and a list + * of block devices associated with each pattern. There are two different + * formats for the operand. First format is pattern={ dev_list }, where elements + * are space separated on the dev_list. Second format is pattern=dev1,dev2,... + * where elements are comma separated on the list of devices. + * + * @param[in] argc number of elements in argv array. + * @param[in] argv command line arguments. + * + * @return STATUS_SUCCESS if successful, otherwise a valid status_t status code. + */ +static status_t _cmdline_ibpi_parse(int argc, char *argv[]) +{ + status_t t_status, ret_status = STATUS_SUCCESS; + + while (optind < argc) { + struct ibpi_state *state = NULL; + char *p = argv[optind++]; + char *t; + t = strchrnul(p, '='); + if (*t != '\0') { + *(t++) = '\0'; + state = _ibpi_state_get(p); + if (state == NULL) { + log_error("%s - unknown pattern name.", p); + return STATUS_INVALID_STATE; + } + if (*t == '{') { + while ((t = argv[optind++]) != NULL) { + if (*t == '}') + break; + t_status = + _ibpi_state_add_block(state, t); + if (t_status != STATUS_SUCCESS) + ret_status = t_status; + } + } else { + while (*(p = t) != '\0') { + t = strchrnul(p, ','); + if (*t != '\0') + *(t++) = '\0'; + t_status = + _ibpi_state_add_block(state, p); + if (t_status != STATUS_SUCCESS) + ret_status = t_status; + } + } + } + } + if (_ibpi_state_determine(&ibpi_list) != STATUS_SUCCESS) + ret_status = STATUS_IBPI_DETERMINE_ERROR; + return ret_status; +} + +/** + * @brief Command line parser - options. + * + * This is internal function of ledctl utility. The function parses options of + * ledctl application. Refer to ledctl help in order to get more information + * about ledctl command line options. + * + * @param[in] argc number of elements in argv array. + * @param[in] argv command line arguments. + * + * @return STATUS_SUCCESS if successful, otherwise a valid status_t status code. + */ +static status_t _cmdline_parse(int argc, char *argv[]) +{ + int opt, opt_index = -1; + status_t status = STATUS_SUCCESS; + + do { + opt = getopt_long(argc, argv, shortopt, longopt, &opt_index); + if (opt == -1) + break; + switch (opt) { + int log_level; + + case 0: + switch (get_option_id(longopt[opt_index].name)) { + case OPT_LOG_LEVEL: + log_level = get_option_id(optarg); + if (log_level != -1) + status = set_verbose_level(log_level); + else + status = STATUS_CMDLINE_ERROR; + break; + default: + status = set_verbose_level( + possible_params[opt_index]); + + } + break; + case 'v': + _ledctl_version(); + exit(EXIT_SUCCESS); + case 'h': + _ledctl_help(); + exit(EXIT_SUCCESS); + case 'l': + status = set_log_path(optarg); + break; + case 'x': + status = STATUS_SUCCESS; + listed_only = 1; + break; + case 'L': + { + struct cntrl_device *ctrl_dev; + + sysfs_init(); + sysfs_scan(); + list_for_each(sysfs_get_cntrl_devices(), ctrl_dev) + print_cntrl(ctrl_dev); + sysfs_reset(); + exit(EXIT_SUCCESS); + } + case ':': + case '?': + default: + log_debug("[opt='%c', opt_index=%d]", opt, opt_index); + return STATUS_CMDLINE_ERROR; + } + opt_index = -1; + if (status != STATUS_SUCCESS) + return status; + } while (1); + + return STATUS_SUCCESS; +} + +/** + * @brief Determine and send IBPI pattern. + * + * This is internal function of ledctl utility. The function determines a state + * of block device based on ibpi_list list. Then it sends a LED control message + * to controller to visualize the pattern. + * + * @param[in] sysfs pointer to sysfs structure holding information + * about the existing controllers, block devices, + * and software RAID devices. + * @param[in] ibpi_local_list TBD + * + * @return STATUS_SUCCESS if successful, otherwise a valid status_t status code. + */ +static status_t _ledctl_execute(struct list *ibpi_local_list) +{ + struct ibpi_state *state; + struct block_device *device; + + if (!listed_only) { + list_for_each(sysfs_get_block_devices(), device) + device->send_fn(device, IBPI_PATTERN_LOCATE_OFF); + } + + list_for_each(ibpi_local_list, state) + list_for_each(&state->block_list, device) + device->send_fn(device, device->ibpi); + + list_for_each(sysfs_get_block_devices(), device) + device->flush_fn(device); + + return STATUS_SUCCESS; +} + +static status_t _read_shared_conf(void) +{ + status_t status; + char share_conf_path[PATH_MAX]; + + memset(share_conf_path, 0, sizeof(share_conf_path)); + snprintf(share_conf_path, sizeof(share_conf_path), "/dev/shm%s", + LEDMON_SHARE_MEM_FILE); + + status = ledmon_read_config(share_conf_path); + return status; +} + +static status_t _init_ledctl_conf(void) +{ + memset(&conf, 0, sizeof(struct ledmon_conf)); + /* initialize with default values */ + conf.log_level = LOG_LEVEL_WARNING; + list_init(&conf.cntrls_whitelist, NULL); + list_init(&conf.cntrls_blacklist, NULL); + + return set_log_path(LEDCTL_DEF_LOG_FILE); +} + +/** + * @brief Application's entry point. + * + * This is the entry point of ledctl utility. This function does all the work. + * It allocates and initializes all used structures. Registers on_exit() + * handlers. + * Then the function parses command line options and commands given and scans + * sysfs tree for controllers, block devices and RAID devices. If no error is + * the function send LED control messages according to IBPI pattern set. + * + * @param[in] argc number of elements in argv array, number of + * command line arguments. + * @param[in] argv array of command line arguments. The last + * element on the list is NULL pointer. + * + * @return STATUS_SUCCESS if successful, otherwise a valid status_t status code. + */ +int main(int argc, char *argv[]) +{ + status_t status; + + setup_options(&longopt, &shortopt, possible_params, + possible_params_size); + set_invocation_name(argv[0]); + openlog(progname, LOG_PERROR, LOG_USER); + + if (getuid() != 0) { + fprintf(stderr, "Only root can run this application.\n"); + return STATUS_NOT_A_PRIVILEGED_USER; + } + + status = _init_ledctl_conf(); + if (status != STATUS_SUCCESS) + return status; + if (on_exit(_ledctl_fini, progname)) + exit(STATUS_ONEXIT_ERROR); + if (_cmdline_parse(argc, argv)) + exit(STATUS_CMDLINE_ERROR); + free(shortopt); + free(longopt); + status = _read_shared_conf(); + if (status != STATUS_SUCCESS) + return status; + status = log_open(conf.log_path); + if (status != STATUS_SUCCESS) + return STATUS_LOG_FILE_ERROR; + + list_init(&ibpi_list, (item_free_t)ibpi_state_fini); + sysfs_init(); + sysfs_scan(); + status = _cmdline_ibpi_parse(argc, argv); + if (status != STATUS_SUCCESS) { + log_debug("main(): _ibpi_parse() failed (status=%s).", + strstatus(status)); + exit(status); + } + return _ledctl_execute(&ibpi_list); +} diff --git a/src/ledmon.c b/src/ledmon.c new file mode 100644 index 0000000..866494a --- /dev/null +++ b/src/ledmon.c @@ -0,0 +1,952 @@ +/* + * Intel(R) Enclosure LED Utilities + * Copyright (C) 2009-2019 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if _HAVE_DMALLOC_H +#include +#endif + +#include "ahci.h" +#include "block.h" +#include "cntrl.h" +#include "config.h" +#include "config_file.h" +#include "ibpi.h" +#include "list.h" +#include "pidfile.h" +#include "raid.h" +#include "scsi.h" +#include "slave.h" +#include "smp.h" +#include "status.h" +#include "sysfs.h" +#include "udev.h" +#include "utils.h" +#include "version.h" +#include "vmdssd.h" + +/** + * @brief List of active block devices. + * + * This list holds all block devices attached to supported storage controllers. + * Only devices which have enclosure management feature enabled are on the + * list, other devices are ignored (except protocol is forced). + */ +static struct list ledmon_block_list; + +/** + * @brief Daemon process termination flag. + * + * This flag indicates that daemon process should terminate. User must send + * SIGTERM to daemon in order to terminate the process gently. + */ +static sig_atomic_t terminate; + +/** + * @brief Path to ledmon configuration file. + * + * This string contains path of the ledmon configuration file. The value is + * set to LEDMON_DEF_CONF_FILE by default and it can be changed by command line + * option. + */ +static char *ledmon_conf_path; + +/** + * @brief Boolean flag whether to run foreground or not. + * + * This flag is turned on with --foreground option. Primary use of this option + * is to use it in systemd service file. + */ +static int foreground; + +/** + * @brief Name of IBPI patterns. + * + * This is internal array with names of IBPI patterns. Logging routines use this + * entries to translate enumeration type into the string. + */ +const char *ibpi_str[] = { + [IBPI_PATTERN_UNKNOWN] = "None", + [IBPI_PATTERN_NORMAL] = "Off", + [IBPI_PATTERN_ONESHOT_NORMAL] = "Oneshot Off", + [IBPI_PATTERN_DEGRADED] = "In a Critical Array", + [IBPI_PATTERN_REBUILD] = "Rebuild", + [IBPI_PATTERN_FAILED_ARRAY] = "In a Failed Array", + [IBPI_PATTERN_HOTSPARE] = "Hotspare", + [IBPI_PATTERN_PFA] = "Predicted Failure Analysis", + [IBPI_PATTERN_FAILED_DRIVE] = "Failure", + [IBPI_PATTERN_LOCATE] = "Locate", + [IBPI_PATTERN_LOCATE_OFF] = "Locate Off", + [IBPI_PATTERN_ADDED] = "Added", + [IBPI_PATTERN_REMOVED] = "Removed" +}; + +/** + * Internal variable of monitor service. It is the pattern used to print out + * information about the version of monitor service. + */ +static char *ledmon_version = "Intel(R) Enclosure LED Monitor Service %d.%d %s\n" + "Copyright (C) 2009-2019 Intel Corporation.\n"; + +/** + * Internal variable of monitor service. It is used to help parse command line + * short options. + */ +static char *shortopt; + +struct option *longopt; + +static int possible_params[] = { + OPT_ALL, + OPT_CONFIG, + OPT_DEBUG, + OPT_ERROR, + OPT_HELP, + OPT_INFO, + OPT_INTERVAL, + OPT_LOG, + OPT_QUIET, + OPT_VERSION, + OPT_WARNING, + OPT_LOG_LEVEL, + OPT_FOREGROUND, +}; + +static int possible_params_size = sizeof(possible_params) + / sizeof(possible_params[0]); + +/** + * @brief Monitor service finalize function. + * + * This is internal function of monitor service. It is used to finalize daemon + * process i.e. free allocated memory, unlock and remove pidfile and close log + * file and syslog. The function is registered as on_exit() handler. + * + * @param[in] status The function ignores this parameter. + * @param[in] program_name The name of the binary file. This argument + * is passed via on_exit() function. + * + * @return The function does not return a value. + */ +static void _ledmon_fini(int __attribute__ ((unused)) status, void *program_name) +{ + sysfs_reset(); + list_erase(&ledmon_block_list); + log_close(); + pidfile_remove(program_name); +} + +/** + * @brief Puts exit status to a log file. + * + * This is internal function of monitor service. It is used to report an exit + * status of the monitor service. The message is logged in to syslog and to log + * file. The function is registered as on_exit() hander. + * + * @param[in] status Status given in the last call to exit() + * function. + * @param[in] arg Argument passed to on_exit(). + * + * @return The function does not return a value. + */ +static void _ledmon_status(int status, void *arg) +{ + int log_level; + char message[4096]; + int ignore = *((int *)arg); + + if (ignore) + return; + + if (status == STATUS_SUCCESS) + log_level = LOG_LEVEL_INFO; + else + log_level = LOG_LEVEL_ERROR; + + snprintf(message, sizeof(message), "exit status is %s.", + strstatus(status)); + + if (get_log_fd() >= 0) + _log(log_level, message); + else + syslog(log_level_infos[log_level].priority, "%s", message); +} + +/** + * @brief Displays the credits. + * + * This is internal function of monitor service. It prints out the name and + * version of the program. It displays the copyright notice and information + * about the author and license, too. + * + * @return The function does not return a value. + */ +static void _ledmon_version(void) +{ + printf(ledmon_version, VERSION_MAJOR, VERSION_MINOR, BUILD_LABEL); + printf("\nThis is free software; see the source for copying conditions." + " There is NO warranty;\nnot even for MERCHANTABILITY or FITNESS" + " FOR A PARTICULAR PURPOSE.\n\n"); +} + +/** + * @brief Displays the help. + * + * This is internal function of monitor service. The function prints the name + * and version of the program out. It displays the usage and available options + * and its arguments (if any). Each option is described. This is an extract + * from user manual page. + * + * @return The function does not return a value. + */ +static void _ledmon_help(void) +{ + printf(ledmon_version, VERSION_MAJOR, VERSION_MINOR, BUILD_LABEL); + printf("\nUsage: %s [OPTIONS]\n\n", progname); + printf("Mandatory arguments for long options are mandatory for short " + "options, too.\n\n"); + print_opt("--interval=VALUE", "-t VALUE", + "Set time interval to VALUE seconds."); + print_opt("", "", "The smallest interval is 5 seconds."); + print_opt("--config=PATH", "-c PATH", + "Use alternate configuration file."); + print_opt("--log=PATH", "-l PATH", + "Use local log file instead /var/log/ledmon.log"); + print_opt("--log-level=VALUE", "-l VALUE", + "Allows user to set ledmon verbose level in logs."); + print_opt("--foreground", "", + "Do not run as daemon."); + print_opt("--help", "-h", "Displays this help text."); + print_opt("--version", "-v", + "Displays version and license information."); + printf("\nRefer to ledmon(8) man page for more detailed description.\n"); + printf("Bugs should be reported at: https://github.com/intel/ledmon/issues\n"); +} + +/** + * @brief Sets the path to configuration file. + * + * This is internal function of monitor service. This function sets the path and + * name of configuration file. The function is checking whether the given path + * is valid or it is invalid and should be ignored. + * + * @param[in] path the new location and name of config file. + * + * @return STATUS_SUCCESS if successful, otherwise a valid status_t status code. + */ +static status_t _set_config_path(char **conf_path, const char *path) +{ + if (!path) + path = LEDMON_DEF_CONF_FILE; + + if (*conf_path) + free(*conf_path); + *conf_path = str_dup(path); + + return STATUS_SUCCESS; +} + +/** + * @brief Sets the value of sleep interval. + * + * This function is used by command line handler to set new value of time + * interval, @see time_interval for details. + * + * @param[in] optarg String containing the new value of time + * interval, given in command line option. + * + * @return STATUS_SUCCESS if successful, otherwise a valid status_t status code. + */ +static status_t _set_sleep_interval(const char *optarg) +{ + conf.scan_interval = atoi(optarg); + if (conf.scan_interval < LEDMON_MIN_SLEEP_INTERVAL) { + log_warning("sleep interval too small... using default."); + conf.scan_interval = LEDMON_DEF_SLEEP_INTERVAL; + } + return STATUS_SUCCESS; +} + + +/** + * @brief Reads config file path and checks if command line input contains + * options which don't require to run ledmon as daemon. + * + * This is internal function of monitor service. This function looks for + * config file path in command line options given to the program from + * command line interface. It also handles options to print help and version. + * + * @param[in] argc - number of arguments. + * @param[in] argv - array of command line arguments. + * + * @return STATUS_SUCCESS if successful, otherwise a valid status_t status code. + */ +static status_t _cmdline_parse_non_daemonise(int argc, char *argv[]) +{ + int opt_index = -1; + int opt = -1; + status_t status = STATUS_SUCCESS; + + do { + opt = getopt_long(argc, argv, shortopt, longopt, &opt_index); + switch (opt) { + case 'c': + status = _set_config_path(&ledmon_conf_path, optarg); + break; + case 'h': + _ledmon_help(); + exit(EXIT_SUCCESS); + case 'v': + _ledmon_version(); + exit(EXIT_SUCCESS); + case ':': + case '?': + return STATUS_CMDLINE_ERROR; + } + } while (opt >= 0); + + return status; +} +/** + * @brief Command line interface handler function. + * + * This is internal function of monitor service. This function interprets the + * options and commands given to the program from command line interface. + * + * @param[in] argc - number of arguments. + * @param[in] argv - array of command line arguments. + * + * @return STATUS_SUCCESS if successful, otherwise a valid status_t status code. + */ +static status_t _cmdline_parse(int argc, char *argv[]) +{ + int opt, opt_index = -1; + status_t status = STATUS_SUCCESS; + + optind = 1; + do { + opt = getopt_long(argc, argv, shortopt, longopt, &opt_index); + if (opt == -1) + break; + if (opt == 'c') + continue; + switch (opt) { + int log_level; + case 0: + switch (get_option_id(longopt[opt_index].name)) { + case OPT_LOG_LEVEL: + log_level = get_option_id(optarg); + if (log_level != -1) + status = set_verbose_level(log_level); + else + status = STATUS_CMDLINE_ERROR; + break; + case OPT_FOREGROUND: + foreground = 1; + break; + default: + status = set_verbose_level( + possible_params[opt_index]); + } + break; + case 'l': + status = set_log_path(optarg); + break; + case 't': + status = _set_sleep_interval(optarg); + break; + } + opt_index = -1; + if (status != STATUS_SUCCESS) + return status; + } while (1); + + return STATUS_SUCCESS; +} + +/** + * @brief SIGTERM handler function. + * + * This is internal function of monitor service. + * + * @param[in] signum - the number of signal received. + * + * @return The function does not return a value. + */ +static void _ledmon_sig_term(int signum) +{ + if (signum == SIGTERM) { + log_info("SIGTERM caught - terminating daemon process."); + terminate = 1; + } +} + +/** + * @brief Configures signal handlers. + * + * This is internal function of monitor services. It sets to ignore SIGALRM, + * SIGHUP and SIGPIPE signals. The function installs a handler for SIGTERM + * signal. User must send SIGTERM to daemon process in order to shutdown the + * daemon gently. + * + * @return The function does not return a value. + */ +static void _ledmon_setup_signals(void) +{ + struct sigaction act; + sigset_t sigset; + + sigemptyset(&sigset); + sigaddset(&sigset, SIGALRM); + sigaddset(&sigset, SIGHUP); + sigaddset(&sigset, SIGTERM); + sigaddset(&sigset, SIGPIPE); + sigaddset(&sigset, SIGUSR1); + sigprocmask(SIG_BLOCK, &sigset, NULL); + + act.sa_handler = SIG_IGN; + act.sa_flags = 0; + sigemptyset(&act.sa_mask); + sigaction(SIGALRM, &act, NULL); + sigaction(SIGHUP, &act, NULL); + sigaction(SIGPIPE, &act, NULL); + act.sa_handler = _ledmon_sig_term; + sigaction(SIGTERM, &act, NULL); + sigaction(SIGUSR1, &act, NULL); + + sigprocmask(SIG_UNBLOCK, &sigset, NULL); +} + +/** + * @brief Puts the calling process into sleep. + * + * This is internal function of monitor service. The function puts the calling + * process into a sleep for the given amount of time (expressed in seconds). The + * function will give control back to the process as soon as time elapses or + * SIGTERM occurs. + * + * @param[in] seconds - the time interval given in seconds. + * + * @return The function does not return a value. + */ +static void _ledmon_wait(int seconds) +{ + int fd, udev_fd, max_fd, res; + fd_set rdfds, exfds; + struct timespec timeout; + sigset_t sigset; + + sigprocmask(SIG_UNBLOCK, NULL, &sigset); + sigdelset(&sigset, SIGTERM); + timeout.tv_nsec = 0; + timeout.tv_sec = seconds; + + fd = open("/proc/mdstat", O_RDONLY); + udev_fd = get_udev_monitor(); + max_fd = MAX(fd, udev_fd) + 1; + do { + FD_ZERO(&rdfds); + FD_ZERO(&exfds); + + if (fd > 0) + FD_SET(fd, &exfds); + if (udev_fd > 0) + FD_SET(udev_fd, &rdfds); + + res = pselect(max_fd, &rdfds, NULL, &exfds, &timeout, &sigset); + if (terminate || !FD_ISSET(udev_fd, &rdfds) || + handle_udev_event(&ledmon_block_list) <= 0) + break; + } while (res > 0); + + if (fd >= 0) + close(fd); +} + +/** + * @brief Determine failed state by comparing saved block device with new + * scanned. + * + * This is internal function of monitor service. Due race conditions related + * with removing files from /sys/block/md* when raid is stopped or disk is + * failed, this function analyse state of every block device between scans. + * + * @param[in] block Pointer to new (scanned) block device + * structure. + * @param[in] temp Pointer to previously saved state of block + * device structure. + * + * @return The function does not return a value. + */ +static void _handle_fail_state(struct block_device *block, + struct block_device *temp) +{ + struct raid_device *temp_raid_device = NULL; + + if (!temp->raid_dev) + /* + * Device is a RAID member now, so keep information about + * related with device RAID. + */ + temp->raid_dev = raid_device_duplicate(block->raid_dev); + + if (!temp->raid_dev) + return; + + temp_raid_device = find_raid_device(sysfs_get_volumes(), + temp->raid_dev->sysfs_path); + + if (!block->raid_dev) { + if (temp->raid_dev->type == DEVICE_TYPE_VOLUME && + temp_raid_device) { + /* + * Device is outside of the volume, but related raid + * still exist, so disk has been removed from volume - + * blink fail LED. It is case when drive is removed + * by mdadm -If. + */ + temp->ibpi = IBPI_PATTERN_FAILED_DRIVE; + /* + * Changing failed state to hotspare will be prevent by + * code from _add_block function. If disk come back to + * container failed state should be removed. By setting + * type to CONTAINER ledmon can react in this case. + */ + temp->raid_dev->type = DEVICE_TYPE_CONTAINER; + } else { + /* + * Device was RAID member and was failed (was outside + * of array and container). Now again is in container. + * Release object to perform hotspare state. + * Or: + * Device is outside of the volume, but related raid is + * removed (or stopped) so device is no longer a RAID + * member. + */ + raid_device_fini(temp->raid_dev); + temp->raid_dev = NULL; + } + } else if (block->raid_dev) { + if (temp->raid_dev->type == DEVICE_TYPE_VOLUME && + block->raid_dev->type == DEVICE_TYPE_CONTAINER) { + /* + * Drive is removed from volume, but still exist + * in container. + */ + enum raid_level new_level; + + if (!temp_raid_device) + new_level = RAID_LEVEL_UNKNOWN; + else + new_level = temp_raid_device->level; + + if ((temp->raid_dev->level == RAID_LEVEL_10 || + temp->raid_dev->level == RAID_LEVEL_1) && + new_level == RAID_LEVEL_0) { + /* + * Device is removed from volume due to + * migration to raid. State of this disk is + * hotspare now. + */ + temp->ibpi = IBPI_PATTERN_HOTSPARE; + } else { + /* + * Trasitions other than raid 0 migration. + * Like reshape, volume stopping etc. + */ + if (temp_raid_device) { + /* + * Drive is removed from volume, + * but still exist in container. This + * situation can be caused by bad + * blocks or calling mdadm + * --set-faulty. + */ + temp->ibpi = IBPI_PATTERN_FAILED_DRIVE; + } + } + } else if (temp->raid_dev->type == DEVICE_TYPE_CONTAINER && + block->raid_dev->type == DEVICE_TYPE_VOLUME) { + /* + * Disk was in container and is added to volume. + * Release object for recreating. + */ + raid_device_fini(temp->raid_dev); + temp->raid_dev = + raid_device_duplicate(block->raid_dev); + } + } +} + +/** + * @brief Adds the block device to list. + * + * This is internal function of monitor service. The function adds a block + * device to the ledmon_block_list list or if the device is already on the list + * it updates the IBPI state of the given device. The function updates timestamp + * value which indicates the time of last structure modification. The function + * is design to be used as 'action' parameter of list_for_each() function. + * Each change of state is logged to the file and to the syslog. + * + * @param[in] block Pointer to block device structure. + * + * @return The function does not return a value. + */ +static void _add_block(struct block_device *block) +{ + struct block_device *temp = NULL; + + list_for_each(&ledmon_block_list, temp) { + if (block_compare(temp, block)) + break; + temp = NULL; + } + if (temp) { + enum ibpi_pattern ibpi = temp->ibpi; + temp->timestamp = block->timestamp; + if (temp->ibpi == IBPI_PATTERN_ADDED) { + temp->ibpi = IBPI_PATTERN_ONESHOT_NORMAL; + } else if (temp->ibpi == IBPI_PATTERN_ONESHOT_NORMAL) { + temp->ibpi = IBPI_PATTERN_UNKNOWN; + } else if (temp->ibpi != IBPI_PATTERN_FAILED_DRIVE) { + if (block->ibpi == IBPI_PATTERN_UNKNOWN) { + if ((temp->ibpi != IBPI_PATTERN_UNKNOWN) && + (temp->ibpi != IBPI_PATTERN_NORMAL)) { + temp->ibpi = + IBPI_PATTERN_ONESHOT_NORMAL; + } else { + temp->ibpi = IBPI_PATTERN_UNKNOWN; + } + } else { + temp->ibpi = block->ibpi; + } + } else if (!(temp->ibpi == IBPI_PATTERN_FAILED_DRIVE && + block->ibpi == IBPI_PATTERN_HOTSPARE) || + (temp->ibpi == IBPI_PATTERN_FAILED_DRIVE && + block->ibpi == IBPI_PATTERN_NONE)) { + temp->ibpi = block->ibpi; + } + + _handle_fail_state(block, temp); + + if (ibpi != temp->ibpi && ibpi <= IBPI_PATTERN_REMOVED) { + log_info("CHANGE %s: from '%s' to '%s'.", + temp->sysfs_path, ibpi2str(ibpi), + ibpi2str(temp->ibpi)); + } + /* Check if name of the device changed.*/ + if (strcmp(temp->sysfs_path, block->sysfs_path)) { + log_info("NAME CHANGED %s to %s", + temp->sysfs_path, block->sysfs_path); + free(temp->sysfs_path); + temp->sysfs_path = str_dup(block->sysfs_path); + } + } else { + /* Device not found, it's a new one! */ + temp = block_device_duplicate(block); + if (temp != NULL) { + log_info("NEW %s: state '%s'.", temp->sysfs_path, + ibpi2str(temp->ibpi)); + list_append(&ledmon_block_list, temp); + } + } +} + +/** + * @brief Sends LED control message. + * + * This is internal function of monitor service. The function sends a LED + * command to storage controller or enclosure device. The function checks + * the time of last modification of block device structure. If the timestamp + * is different then the current global timestamp this means the device is + * missing due to hot-remove or hardware failure so it must be reported on + * LEDs appropriately. Note that controller device and host attached to this + * block device points to invalid pointer so it must be 'refreshed'. + * + * @param[in] block Pointer to block device structure. + * + * @return The function does not return a value. + */ +static void _send_msg(struct block_device *block) +{ + if (!block->cntrl) { + log_debug("Missing cntrl for dev: %s. Not sending anything.", + strstr(block->sysfs_path, "host")); + return; + } + if (block->timestamp != timestamp || + block->ibpi == IBPI_PATTERN_REMOVED) { + if (block->ibpi != IBPI_PATTERN_FAILED_DRIVE) { + log_info("CHANGE %s: from '%s' to '%s'.", + block->sysfs_path, ibpi2str(block->ibpi), + ibpi2str(IBPI_PATTERN_FAILED_DRIVE)); + block->ibpi = IBPI_PATTERN_FAILED_DRIVE; + } else { + char *host = strstr(block->sysfs_path, "host"); + log_debug("DETACHED DEV '%s' in failed state", + host ? host : block->sysfs_path); + } + } + block->send_fn(block, block->ibpi); + block->ibpi_prev = block->ibpi; +} + +static void _flush_msg(struct block_device *block) +{ + if (!block->cntrl) + return; + block->flush_fn(block); +} + +static void _revalidate_dev(struct block_device *block) +{ + /* Bring back controller and host to the device. */ + block->cntrl = block_get_controller(sysfs_get_cntrl_devices(), + block->cntrl_path); + if (!block->cntrl) { + /* It could be removed VMD drive */ + log_debug("Failed to get controller for dev: %s, ctrl path: %s", + block->sysfs_path, block->cntrl_path); + return; + } + if (block->cntrl->cntrl_type == CNTRL_TYPE_SCSI) { + block->host = block_get_host(block->cntrl, block->host_id); + if (block->host) { + if (dev_directly_attached(block->sysfs_path)) + cntrl_init_smp(NULL, block->cntrl); + else + scsi_get_enclosure(block); + } else { + log_debug("Failed to get host for dev: %s, hostId: %d", + block->sysfs_path, block->host_id); + /* If failed, invalidate cntrl */ + block->cntrl = NULL; + } + } + return; +} + +static void _invalidate_dev(struct block_device *block) +{ + /* Those fields are valid only per 'session' - through single scan. */ + block->cntrl = NULL; + block->host = NULL; + block->enclosure = NULL; + block->encl_index = -1; +} + +static void _check_block_dev(struct block_device *block, int *restart) +{ + if (!block->cntrl) { + (*restart)++; + } +} + +/** + * @brief Sets a list of block devices and sends LED control messages. + * + * This is internal function of monitor service. Based on current layout of + * sysfs tree the function extracts block devices and for each block device it + * send LED control message to storage controller or enclosure. The message is + * determine by appropriate field in block device's structure. See _add_block() + * and _send_msg() functions description for more details. + * + * @return The function does not return a value. + */ +static void _ledmon_execute(void) +{ + int restart = 0; /* ledmon_block_list needs restart? */ + struct block_device *device; + + /* Revalidate each device in the list. Bring back controller and host */ + list_for_each(&ledmon_block_list, device) + _revalidate_dev(device); + /* Scan all devices and compare them against saved list */ + list_for_each(sysfs_get_block_devices(), device) + _add_block(device); + /* Send message to all devices in the list if needed. */ + list_for_each(&ledmon_block_list, device) + _send_msg(device); + /* Flush unsent messages from internal buffers. */ + list_for_each(&ledmon_block_list, device) + _flush_msg(device); + /* Check if there is any orphaned device. */ + list_for_each(&ledmon_block_list, device) + _check_block_dev(device, &restart); + + if (restart) { + /* there is at least one detached element in the list. */ + list_erase(&ledmon_block_list); + } +} + +static status_t _init_ledmon_conf(void) +{ + memset(&conf, 0, sizeof(struct ledmon_conf)); + + /* initialize with default values */ + conf.blink_on_init = 1; + conf.blink_on_migration = 1; + conf.rebuild_blink_on_all = 0; + conf.raid_members_only = 0; + conf.log_level = LOG_LEVEL_WARNING; + conf.scan_interval = LEDMON_DEF_SLEEP_INTERVAL; + list_init(&conf.cntrls_whitelist, NULL); + list_init(&conf.cntrls_blacklist, NULL); + return set_log_path(LEDMON_DEF_LOG_FILE); +} + +static void _close_parent_fds(void) +{ + struct list dir; + + if (scan_dir("/proc/self/fd", &dir) == 0) { + char *elem; + + list_for_each(&dir, elem) { + int fd = (int)strtol(basename(elem), NULL, 10); + + if (fd != get_log_fd()) + close(fd); + } + list_erase(&dir); + } +} + +/** + */ +int main(int argc, char *argv[]) +{ + status_t status = STATUS_SUCCESS; + int ignore = 0; + + setup_options(&longopt, &shortopt, possible_params, + possible_params_size); + set_invocation_name(argv[0]); + openlog(progname, LOG_PID | LOG_PERROR, LOG_DAEMON); + + if (on_exit(_ledmon_status, &ignore)) + return STATUS_ONEXIT_ERROR; + + if (_cmdline_parse_non_daemonise(argc, argv) != STATUS_SUCCESS) + return STATUS_CMDLINE_ERROR; + + if (getuid() != 0) { + fprintf(stderr, "Only root can run this application.\n"); + return STATUS_NOT_A_PRIVILEGED_USER; + } + + status = _init_ledmon_conf(); + if (status != STATUS_SUCCESS) + return status; + + status = ledmon_read_config(ledmon_conf_path); + if (status != STATUS_SUCCESS) + return status; + + if (_cmdline_parse(argc, argv) != STATUS_SUCCESS) + return STATUS_CMDLINE_ERROR; + + ledmon_write_shared_conf(); + + if (log_open(conf.log_path) != STATUS_SUCCESS) + return STATUS_LOG_FILE_ERROR; + + free(shortopt); + free(longopt); + if (pidfile_check(progname, NULL) == 0) { + log_warning("daemon is running..."); + return STATUS_LEDMON_RUNNING; + } + if (!foreground) { + pid_t pid = fork(); + + if (pid < 0) { + log_debug("main(): fork() failed (errno=%d).", errno); + exit(EXIT_FAILURE); + } + if (pid > 0) { + ignore = 1; /* parent: don't print exit status */ + exit(EXIT_SUCCESS); + } + + pid_t sid = setsid(); + + if (sid < 0) { + log_debug("main(): setsid() failed (errno=%d).", errno); + exit(EXIT_FAILURE); + } + + _close_parent_fds(); + + int t = open("/dev/null", O_RDWR); + UNUSED(dup(t)); + UNUSED(dup(t)); + } + + umask(027); + + if (chdir("/") < 0) { + log_debug("main(): chdir() failed (errno=%d).", errno); + exit(EXIT_FAILURE); + } + if (pidfile_create(progname)) { + log_debug("main(): pidfile_creat() failed."); + exit(EXIT_FAILURE); + } + _ledmon_setup_signals(); + + if (on_exit(_ledmon_fini, progname)) + exit(STATUS_ONEXIT_ERROR); + list_init(&ledmon_block_list, (item_free_t)block_device_fini); + sysfs_init(); + log_info("monitor service has been started..."); + while (terminate == 0) { + struct block_device *device; + + timestamp = time(NULL); + sysfs_scan(); + _ledmon_execute(); + _ledmon_wait(conf.scan_interval); + /* Invalidate each device in the list. Clear controller and host. */ + list_for_each(&ledmon_block_list, device) + _invalidate_dev(device); + sysfs_reset(); + } + ledmon_remove_shared_conf(); + stop_udev_monitor(); + exit(EXIT_SUCCESS); +} diff --git a/src/list.c b/src/list.c new file mode 100644 index 0000000..d7b09a9 --- /dev/null +++ b/src/list.c @@ -0,0 +1,85 @@ +/* + * Intel(R) Enclosure LED Utilities + * Copyright (C) 2009-2018 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#include + +#include "list.h" +#include "utils.h" + +void __list_erase(struct list *list, item_free_t free_fn) +{ + struct node *node; + + list_for_each_node(list, node) { + if (free_fn) + free_fn(node->item); + free(node); + } + list->head = list->tail = NULL; +} + +void __list_remove(struct node *node, item_free_t free_fn) +{ + struct list *list = node->list; + + if (node->prev) + node->prev->next = node->next; + else + list->head = node->next; + if (node->next) + node->next->prev = node->prev; + else + list->tail = node->prev; + node->list = NULL; + node->next = NULL; + node->prev = NULL; + + if (free_fn) + free_fn(node->item); +} + +void list_insert(struct list *list, void *item, struct node *after) +{ + struct node *new; + struct node **x; + + new = malloc(sizeof(struct node)); + if (!new) { + log_error("Failed to allocate memory for list node."); + exit(1); + } + + new->list = list; + new->item = item; + + if (after) { + assert(list == after->list); + x = &after->next; + } else { + x = &list->head; + } + + if (*x == NULL) + list->tail = new; + else + (*x)->prev = new; + new->next = *x; + *x = new; + new->prev = after; +} diff --git a/src/list.h b/src/list.h new file mode 100644 index 0000000..87b8850 --- /dev/null +++ b/src/list.h @@ -0,0 +1,236 @@ +/* + * Intel(R) Enclosure LED Utilities + * Copyright (C) 2009-2018 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#ifndef _LIST_H_INCLUDED_ +#define _LIST_H_INCLUDED_ + +#include + +struct node { + struct node *next, *prev; + struct list *list; + void *item; +}; + +typedef void (*item_free_t)(void *); + +struct list { + struct node *head, *tail; + item_free_t item_free; +}; + +#define __list_for_each_node(__list, __node, __start_fn, __iter_fn) \ + for (struct node *__n = __start_fn(__list), *__next; \ + __n && (__node = __n) && ((__next = __iter_fn(__n)) || (!__next)); \ + __n = __next) + +#define list_for_each_node(__list, __node) \ + __list_for_each_node(__list, __node, list_head, list_next) + +#define list_for_each_node_reverse(__list, __node) \ + __list_for_each_node(__list, __node, list_tail, list_prev) + +#define __list_for_each(__list, __item, __start_fn, __iter_fn) \ + for (struct node *__node = __start_fn(__list); \ + __node && ((__item = __node->item) || (!__node->item)); \ + __node = __iter_fn(__node)) + +#define list_for_each(__list, __item) \ + __list_for_each(__list, __item, list_head, list_next) + +#define list_for_each_reverse(__list, __item) \ + __list_for_each(__list, __item, list_tail, list_prev) + +/** + * @brief Initializes a list object. + * + * Initializes a list object to reflect an empty state. + * + * @param[in] list pointer to a list object. + * @param[in] item_free_fn custom callback for deallocating list items. + * If NULL, free() will be used. + */ +static inline void list_init(struct list *list, item_free_t item_free_fn) +{ + list->head = NULL; + list->tail = NULL; + if (item_free_fn) + list->item_free = item_free_fn; + else + list->item_free = free; +} + +/** + * @brief Clears a list and frees the items it contains. + * + * This function releases the memory allocated for a list object. It also frees + * the data items attached to list nodes. It does not free the list itself. + * + * @param[in] list pointer to a list object. + */ +static inline void list_erase(struct list *list) +{ + void __list_erase(struct list *list, item_free_t free_fn); + __list_erase(list, list->item_free); +} + +/** + * @brief Clears a list. + * + * This function removes and deallocates all nodes from the list. It does not + * free the data items, to do that use list_erase(). + * + * @param[in] list pointer to a list object. + */ +static inline void list_clear(struct list *list) +{ + void __list_erase(struct list *list, item_free_t free_fn); + __list_erase(list, NULL); +} + +/** + * @brief Removes an element from the list. + * + * This function removes an element from the list. It only detaches the element + * and does not release the memory allocated for the element. To free memory + * allocated for an element use list_delete() instead. + * + * @param[in] node pointer to a node object. + */ +static inline void list_remove(struct node *node) +{ + void __list_remove(struct node *node, item_free_t free_fn); + __list_remove(node, NULL); +} + +/** + * @brief Removes an element from the list and releases its memory. + * + * This function removes an element from the list and frees the memory allocated + * for the list node and data item. + * + * @param[in] node pointer to a node object. + */ +static inline void list_delete(struct node *node) +{ + void __list_remove(struct node *node, item_free_t free_fn); + __list_remove(node, node->list->item_free); + free(node); +} + +/** + * @brief Inserts an element into the list. + * + * This function puts an element after a given element. + * + * @param[in] list pointer to list object. + * @param[in] item data item to be inserted into the list. + * @param[in] after list node after which to insert the element. + * If NULL, then insert at the head of the list. + */ +void list_insert(struct list *list, void *item, struct node *after); + +/** + * @brief Appends an element to the end of the list. + * + * This function puts an element on tail of a list. + * + * @param[in] list pointer to list object. + * @param[in] item data item to be inserted into the list. + */ +static inline void list_append(struct list *list, void *item) +{ + list_insert(list, item, list->tail); +} + +/** + * @brief Reruns next element. + * + * This function returns next element relatively to the given element. + * + * @param[in] node pointer to a node object. + * + * @return Pointer to an element if successful. The NULL pointer means + * that node is the last element on the list. + */ +static inline struct node *list_next(const struct node *node) +{ + return node->next; +} + +/** + * @brief Returns previous element. + * + * This function returns previous element relatively to the given element. + * + * @param[in] node pointer to a node object. + * + * @return Pointer to an element if successful. The NULL pointer means + * that node is the first element on the list. + */ +static inline struct node *list_prev(const struct node *node) +{ + return node->prev; +} + +/** + * @brief Returns head of a list. + * + * This function returns a head of a list. + * + * @param[in] list pointer to a list object. + * + * @return Pointer to an element if successful. The NULL pointer means that + * there's no element on a list. + */ +static inline struct node *list_head(const struct list *list) +{ + return list->head; +} + +/** + * @brief Returns tail of a list. + * + * This function returns a tail of a list. + * + * @param[in] list pointer to a list object. + * + * @return Pointer to an element if successful. The NULL pointer means that + * there's no element on a list. + */ +static inline struct node *list_tail(const struct list *list) +{ + return list->tail; +} + +/** + * @brief Checks if a list is empty. + * + * This function checks if a list object has elements. + * + * @param[in] list pointer to a list object. + * + * @return 1 if list is empty, otherwise the function returns 0. + */ +static inline int list_is_empty(const struct list *list) +{ + return (list->head == NULL); +} + +#endif /* _LIST_H_INCLUDED_ */ diff --git a/src/npem.c b/src/npem.c new file mode 100644 index 0000000..23b518d --- /dev/null +++ b/src/npem.c @@ -0,0 +1,241 @@ +/* + * Intel(R) Enclosure LED Utilities + * Copyright (C) 2019-2020 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#include +#include +#include +#include +#include + +#include "config.h" +#include "cntrl.h" +#include "npem.h" +#include "utils.h" + +#define PCI_EXT_CAP_ID_NPEM 0x29 /* Native PCIe Enclosure Management */ + +#define PCI_NPEM_CAP_REG 0x04 /* NPEM Capability Register */ +#define PCI_NPEM_CTRL_REG 0x08 /* NPEM Control Register */ +#define PCI_NPEM_STATUS_REG 0x0C /* NPEM Status Register */ + +/* NPEM Capable/Enable */ +#define PCI_NPEM_CAP 0x001 +/* NPEM OK Capable/Control */ +#define PCI_NPEM_OK_CAP 0x004 +/* NPEM Locate Capable/Control */ +#define PCI_NPEM_LOCATE_CAP 0x008 +/* NPEM Fail Capable/Control */ +#define PCI_NPEM_FAIL_CAP 0x010 +/* NPEM Rebuild Capable/Control */ +#define PCI_NPEM_REBUILD_CAP 0x020 +/* NPEM Predicted Failure Analysis Capable/Control */ +#define PCI_NPEM_PFA_CAP 0x040 +/* NPEM Hot Spare Capable/Control */ +#define PCI_NPEM_HOT_SPARE_CAP 0x080 +/* NPEM in a Critical Array Capable/Control */ +#define PCI_NPEM_CRA_CAP 0x100 +/* NPEM in a Failed Array Capable/Control */ +#define PCI_NPEM_FA_CAP 0x200 +/* NPEM reserved and enclosure specific */ +#define PCI_NPEM_RESERVED ~0xfff + +#define PCI_NPEM_STATUS_CC 0x01 /* NPEM Command Completed */ + +const int ibpi_to_npem_capability[] = { + [IBPI_PATTERN_NORMAL] = PCI_NPEM_OK_CAP, + [IBPI_PATTERN_ONESHOT_NORMAL] = PCI_NPEM_OK_CAP, + [IBPI_PATTERN_DEGRADED] = PCI_NPEM_CRA_CAP, + [IBPI_PATTERN_HOTSPARE] = PCI_NPEM_HOT_SPARE_CAP, + [IBPI_PATTERN_REBUILD] = PCI_NPEM_REBUILD_CAP, + [IBPI_PATTERN_FAILED_ARRAY] = PCI_NPEM_FA_CAP, + [IBPI_PATTERN_PFA] = PCI_NPEM_PFA_CAP, + [IBPI_PATTERN_FAILED_DRIVE] = PCI_NPEM_FAIL_CAP, + [IBPI_PATTERN_LOCATE] = PCI_NPEM_LOCATE_CAP, + [IBPI_PATTERN_LOCATE_OFF] = PCI_NPEM_OK_CAP, +}; + +static struct pci_access *get_pci_access() +{ + struct pci_access *pacc; + + pacc = pci_alloc(); + pci_init(pacc); + + return pacc; +} + +static struct pci_dev *get_pci_dev(struct pci_access *pacc, const char *path) +{ + unsigned int domain, bus, dev, fn; + char *p = strrchr(path, '/'); + + if (!p) + return NULL; + + if (sscanf(p + 1, "%x:%x:%x.%x", &domain, &bus, &dev, &fn) != 4) + return NULL; + + return pci_get_dev(pacc, domain, bus, dev, fn); +} + +static struct pci_cap *get_npem_cap(struct pci_dev *pdev) +{ + return pci_find_cap(pdev, PCI_EXT_CAP_ID_NPEM, PCI_CAP_EXTENDED); +} + +static u32 read_npem_register(struct pci_dev *pdev, int reg) +{ + u32 val = 0; + struct pci_cap *pcap = get_npem_cap(pdev); + + if (!pcap) + return val; + + return pci_read_long(pdev, pcap->addr + reg); +} + +static int write_npem_register(struct pci_dev *pdev, int reg, u32 val) +{ + struct pci_cap *pcap = get_npem_cap(pdev); + + if (!pcap) + return val; + + return pci_write_long(pdev, pcap->addr + reg, val); +} + +int is_npem_capable(const char *path) +{ + u8 val; + struct pci_access *pacc = get_pci_access(); + struct pci_dev *pdev; + + if (!pacc) + return 0; + + pdev = get_pci_dev(pacc, path); + + if (!pdev) { + pci_cleanup(pacc); + return 0; + } + + val = read_npem_register(pdev, PCI_NPEM_CAP_REG); + + pci_free_dev(pdev); + pci_cleanup(pacc); + return (val & PCI_NPEM_CAP); +} + +static int npem_wait_command(struct pci_dev *pdev) +{ +/* + * Software must wait for an NPEM command to complete before issuing + * the next NPEM command. However, if this bit is not set within + * 1 second limit on command execution, software is permitted to repeat + * the NPEM command or issue the next NPEM command. + * PCIe r4.0 sec 7.9.20.4 + * + * Poll the status register until the Command Completed bit becomes set + * or timeout is reached. + */ + time_t start, end; + u32 reg; + + time(&start); + end = start; + while (difftime(start, end) < 1) { + reg = read_npem_register(pdev, PCI_NPEM_STATUS_REG); + + if (reg & PCI_NPEM_STATUS_CC) { + /* status register type is RW1C */ + write_npem_register(pdev, PCI_NPEM_STATUS_REG, + PCI_NPEM_STATUS_CC); + return 0; + } + time(&end); + } + return 1; +} + +int npem_write(struct block_device *device, enum ibpi_pattern ibpi) +{ + struct cntrl_device *npem_cntrl = device->cntrl; + struct pci_access *pacc = NULL; + struct pci_dev *pdev = NULL; + + u32 reg; + u32 val; + + int err = 0; + + if (ibpi == device->ibpi_prev) + return 0; + + if ((ibpi < IBPI_PATTERN_NORMAL) || (ibpi > IBPI_PATTERN_LOCATE_OFF)) { + err = -EINVAL; + goto exit; + } + + pacc = get_pci_access(); + if (!pacc) { + log_error("NPEM: Unable to initialize pci access for %s\n", + npem_cntrl->sysfs_path); + err = -ENOMEM; + goto exit; + } + + pdev = get_pci_dev(pacc, npem_cntrl->sysfs_path); + if (!pdev) { + log_error("NPEM: Unable to get pci device for %s\n", + npem_cntrl->sysfs_path); + err = -ENXIO; + goto exit; + } + + reg = read_npem_register(pdev, PCI_NPEM_CAP_REG); + if ((reg & ibpi_to_npem_capability[ibpi]) == 0) { + log_debug("NPEM: Controller %s doesn't support %s pattern\n", + npem_cntrl->sysfs_path, ibpi_str[ibpi]); + ibpi = IBPI_PATTERN_NORMAL; + } + + reg = read_npem_register(pdev, PCI_NPEM_CTRL_REG); + val = (reg & PCI_NPEM_RESERVED); + val = (val | PCI_NPEM_CAP | ibpi_to_npem_capability[ibpi]); + + write_npem_register(pdev, PCI_NPEM_CTRL_REG, val); + if (npem_wait_command(pdev)) { + log_error("NPEM: Write timeout for %s\n", + npem_cntrl->sysfs_path); + err = -EAGAIN; + } + +exit: + if (pdev) + pci_free_dev(pdev); + if (pacc) + pci_cleanup(pacc); + return err; +} + +char *npem_get_path(const char *cntrl_path) +{ + return str_dup(cntrl_path); +} diff --git a/src/npem.h b/src/npem.h new file mode 100644 index 0000000..5fa3f11 --- /dev/null +++ b/src/npem.h @@ -0,0 +1,29 @@ +/* + * Intel(R) Enclosure LED Utilities + * Copyright (C) 2019-2020 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#ifndef NPEM_H_INCLUDED_ +#define NPEM_H_INCLUDED_ +#include "block.h" +#include "ibpi.h" + +int is_npem_capable(const char *path); +int npem_write(struct block_device *device, enum ibpi_pattern ibpi); +char *npem_get_path(const char *cntrl_path); + +#endif // NPEM_H_INCLUDED_ diff --git a/src/pci_slot.c b/src/pci_slot.c new file mode 100644 index 0000000..b15e194 --- /dev/null +++ b/src/pci_slot.c @@ -0,0 +1,68 @@ +/* + * Intel(R) Enclosure LED Utilities + * Copyright (C) 2016-2018 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#include +#include +#include +#include +#include + +#if _HAVE_DMALLOC_H +#include +#endif + +#include "config.h" +#include "pci_slot.h" +#include "utils.h" + +/* + * Allocates memory for PCI hotplug slot structure and initializes fields of + * the structure. + */ +struct pci_slot *pci_slot_init(const char *path) +{ + struct pci_slot *result = NULL; + + result = malloc(sizeof(struct pci_slot)); + if (result == NULL) + return NULL; + result->sysfs_path = str_dup(path); + result->address = get_text(path, "address"); + result->attention = get_int(path, -1, "attention"); + + if (result->attention == -1) { + pci_slot_fini(result); + return NULL; + } + + return result; +} + +/* + * The function returns memory allocated for fields of hotplug slot structure + * to the system. + */ +void pci_slot_fini(struct pci_slot *slot) +{ + if (slot) { + free(slot->sysfs_path); + free(slot->address); + free(slot); + } +} diff --git a/src/pci_slot.h b/src/pci_slot.h new file mode 100644 index 0000000..b971de3 --- /dev/null +++ b/src/pci_slot.h @@ -0,0 +1,72 @@ +/* + * Intel(R) Enclosure LED Utilities + * Copyright (C) 2016-2017 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#ifndef PCI_SLOT_H_INCLUDED_ +#define PCI_SLOT_H_INCLUDED_ + +/** + * @brief PCI hotplug slot structure. + * + * This structure describes a PCI hotplug slot exposed by the hotplug driver. + */ +struct pci_slot { + /** + * Path to PCI hotplug slot in sysfs tree. + */ + char *sysfs_path; + + /** + * PCI hotplug slot address. + */ + char *address; + + /** + * State of the Amber LED of the PCI slot. + */ + int attention; +}; + +/** + * @brief Allocates memory for a PCI hotplug slot structure. + * + * This function allocates memory for a new structure describing hotplug slot. + * It reads the sysfs entries and populates structure fields. + * + * @param[in] path Path to a PCI hotplug slot in sysfs tree. + * The path begins with "/sys/bus/pci/slots/". + * + * @return Pointer to PCI hotplug slot structure if successful, otherwise the + * function returns NULL pointer. The NULL pointer means either the + * specified path is invalid or there's not enough memory in the system + * to allocated new structure. + */ +struct pci_slot *pci_slot_init(const char *path); + +/** + * @brief Releases an PCI hotplug slot structure. + * + * This function releases memory allocated for PCI hotplug slotstructure. + * + * @param[in] slot Pointer to PCI hotplug slot structure. + * + * @return The function does not return a value. + */ +void pci_slot_fini(struct pci_slot *slot); + +#endif // PCI_SLOT_H_INCLUDED_ diff --git a/src/pidfile.c b/src/pidfile.c new file mode 100644 index 0000000..48c8a6e --- /dev/null +++ b/src/pidfile.c @@ -0,0 +1,107 @@ +/* + * Intel(R) Enclosure LED Utilities + * Copyright (C) 2009-2019 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if _HAVE_DMALLOC_H +#include +#endif + +#include "config.h" +#include "pidfile.h" +#include "status.h" +#include "utils.h" + +#define RUN_DIR "/var/run/" +#define PID ".pid" + +/** + */ +status_t pidfile_create(const char *name) +{ + char buf[PATH_MAX]; + int fd, count; + + snprintf(buf, sizeof(buf), "%s/%s%s", RUN_DIR, name, PID); + + fd = open(buf, O_WRONLY | O_CREAT, 0640); + if (fd < 0) + return STATUS_FILE_OPEN_ERROR; + if (lockf(fd, F_TLOCK, 0) < 0) { + close(fd); + return STATUS_FILE_LOCK_ERROR; + } + sprintf(buf, "%d\n", getpid()); + count = write(fd, buf, strlen(buf)); + close(fd); + return (count < 0) ? STATUS_FILE_WRITE_ERROR : STATUS_SUCCESS; +} + +/** + */ +int pidfile_remove(const char *name) +{ + char buf[PATH_MAX]; + + snprintf(buf, sizeof(buf), "%s/%s%s", RUN_DIR, name, PID); + return unlink(buf); +} + +/** + * @brief Test whether process with given pid is still alive + * + * @return STATUS_SUCCESS if proces is alive and other error if not or + * if there is an error + */ +int ping_proc(pid_t pid) +{ + if (pid <= 0) + return STATUS_INVALID_PATH; + if (kill(pid, 1) == 0) + return STATUS_SUCCESS; + return STATUS_INVALID_PATH; +} + +/** + */ +status_t pidfile_check(const char *name, pid_t *pid) +{ + char path[PATH_MAX], *p; + pid_t tp; + + snprintf(path, sizeof(path), "%s/%s%s", RUN_DIR, name, PID); + + p = buf_read(path); + if (p == NULL) + return STATUS_INVALID_PATH; + tp = atoi(p); + if (pid) + *pid = tp; + free(p); + return ping_proc(tp); +} diff --git a/src/pidfile.h b/src/pidfile.h new file mode 100644 index 0000000..802c436 --- /dev/null +++ b/src/pidfile.h @@ -0,0 +1,39 @@ +/* + * Intel(R) Enclosure LED Utilities + * Copyright (C) 2009-2017 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#ifndef _PIDFILE_H_INCLUDED_ +#define _PIDFILE_H_INCLUDED_ + +#include + +#include "status.h" + +/** + */ +status_t pidfile_create(const char *name); + +/** + */ +int pidfile_remove(const char *name); + +/** + */ +status_t pidfile_check(const char *name, pid_t *pid); + +#endif /* _PIDFILE_H_INCLUDED_ */ diff --git a/src/raid.c b/src/raid.c new file mode 100644 index 0000000..3b1ddba --- /dev/null +++ b/src/raid.c @@ -0,0 +1,199 @@ +/* + * Intel(R) Enclosure LED Utilities + * Copyright (C) 2009-2019 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#include +#include +#include +#include + +#if _HAVE_DMALLOC_H +#include +#endif + +#include "block.h" +#include "config.h" +#include "ibpi.h" +#include "list.h" +#include "raid.h" +#include "slave.h" +#include "status.h" +#include "sysfs.h" +#include "utils.h" + +/** + */ +static enum raid_state _get_array_state(const char *path) +{ + enum raid_state state = RAID_STATE_UNKNOWN; + + char *p = get_text(path, "md/array_state"); + if (p) { + if (strcmp(p, "clear") == 0) + state = RAID_STATE_CLEAR; + else if (strcmp(p, "inactive") == 0) + state = RAID_STATE_INACTIVE; + else if (strcmp(p, "suspended") == 0) + state = RAID_STATE_SUSPENDED; + else if (strcmp(p, "readonly") == 0) + state = RAID_STATE_READONLY; + else if (strcmp(p, "read-auto") == 0) + state = RAID_STATE_READ_AUTO; + else if (strcmp(p, "clean") == 0) + state = RAID_STATE_CLEAN; + else if (strcmp(p, "active") == 0) + state = RAID_STATE_ACTIVE; + else if (strcmp(p, "write-pending") == 0) + state = RAID_STATE_WRITE_PENDING; + else if (strcmp(p, "active-idle") == 0) + state = RAID_STATE_ACTIVE_IDLE; + free(p); + } + return state; +} + +/** + */ +static enum raid_action _get_sync_action(const char *path) +{ + enum raid_action action = RAID_ACTION_UNKNOWN; + + char *p = get_text(path, "md/sync_action"); + if (p) { + if (strcmp(p, "idle") == 0) + action = RAID_ACTION_IDLE; + else if (strcmp(p, "reshape") == 0) + action = RAID_ACTION_RESHAPE; + else if (strcmp(p, "frozen") == 0) + action = RAID_ACTION_FROZEN; + else if (strcmp(p, "resync") == 0) + action = RAID_ACTION_RESYNC; + else if (strcmp(p, "check") == 0) + action = RAID_ACTION_CHECK; + else if (strcmp(p, "recover") == 0) + action = RAID_ACTION_RECOVER; + else if (strcmp(p, "repair") == 0) + action = RAID_ACTION_REPAIR; + free(p); + } + return action; +} + +/** + */ +static enum raid_level _get_level(const char *path) +{ + enum raid_level result = RAID_LEVEL_UNKNOWN; + + char *p = get_text(path, "md/level"); + if (p) { + if (strcmp(p, "raid0") == 0) + result = RAID_LEVEL_0; + else if (strcmp(p, "raid1") == 0) + result = RAID_LEVEL_1; + else if (strcmp(p, "raid10") == 0) + result = RAID_LEVEL_10; + else if (strcmp(p, "raid4") == 0) + result = RAID_LEVEL_4; + else if (strcmp(p, "raid5") == 0) + result = RAID_LEVEL_5; + else if (strcmp(p, "raid6") == 0) + result = RAID_LEVEL_6; + else if (strcmp(p, "linear") == 0) + result = RAID_LEVEL_LINEAR; + else if (strcmp(p, "faulty") == 0) + result = RAID_LEVEL_FAULTY; + free(p); + } + return result; +} + +/** + */ +struct raid_device *raid_device_init(const char *path, unsigned int device_num, + enum device_type type) +{ + struct raid_device *device = NULL; + enum raid_state state; + const char *debug_dev; + + state = _get_array_state(path); + if (state > RAID_STATE_INACTIVE || + (type == DEVICE_TYPE_CONTAINER && state > RAID_STATE_CLEAR)) { + device = malloc(sizeof(struct raid_device)); + if (device) { + device->sysfs_path = str_dup(path); + device->device_num = device_num; + device->sync_action = _get_sync_action(path); + device->array_state = state; + device->level = _get_level(path); + device->degraded = get_int(path, -1, "md/degraded"); + device->raid_disks = get_int(path, 0, "md/raid_disks"); + device->type = type; + debug_dev = strrchr(path, '/'); + debug_dev = debug_dev ? debug_dev + 1 : path; + log_debug("(%s) path: %s, level=%d, state=%d, " \ + "degraded=%d, disks=%d, type=%d", + __func__, debug_dev, device->level, + state, device->degraded, + device->raid_disks, type); + } + } + return device; +} + +/** + */ +void raid_device_fini(struct raid_device *device) +{ + if (device) { + if (device->sysfs_path) + free(device->sysfs_path); + free(device); + } +} + +/** + */ +struct raid_device *raid_device_duplicate(struct raid_device *device) +{ + struct raid_device *new_device = NULL; + + if (device) { + new_device = malloc(sizeof(struct raid_device)); + if (new_device) { + *new_device = *device; + new_device->sysfs_path = str_dup(device->sysfs_path); + } + } + return new_device; +} + +/** + */ +struct raid_device *find_raid_device(const struct list *raid_list, + char *raid_sysfs_path) +{ + struct raid_device *raid = NULL; + + list_for_each(raid_list, raid) { + if (strcmp(raid->sysfs_path, raid_sysfs_path) == 0) + return raid; + } + return NULL; +} diff --git a/src/raid.h b/src/raid.h new file mode 100644 index 0000000..dd72737 --- /dev/null +++ b/src/raid.h @@ -0,0 +1,103 @@ +/* + * Intel(R) Enclosure LED Utilities + * Copyright (C) 2009-2018 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#ifndef _RAID_H_INCLUDED_ +#define _RAID_H_INCLUDED_ + +/** + */ +enum raid_state { + RAID_STATE_UNKNOWN = 0, + RAID_STATE_CLEAR, + RAID_STATE_INACTIVE, + RAID_STATE_SUSPENDED, + RAID_STATE_READONLY, + RAID_STATE_READ_AUTO, + RAID_STATE_CLEAN, + RAID_STATE_ACTIVE, + RAID_STATE_WRITE_PENDING, + RAID_STATE_ACTIVE_IDLE +}; + +/** + */ +enum raid_level { + RAID_LEVEL_UNKNOWN = 0, + RAID_LEVEL_0, + RAID_LEVEL_1, + RAID_LEVEL_10, + RAID_LEVEL_4, + RAID_LEVEL_5, + RAID_LEVEL_6, + RAID_LEVEL_FAULTY, + RAID_LEVEL_LINEAR +}; + +/** + */ +enum device_type { + DEVICE_TYPE_UNKNOWN = 0, + DEVICE_TYPE_VOLUME, + DEVICE_TYPE_CONTAINER +}; + +/** + */ +enum raid_action { + RAID_ACTION_UNKNOWN = 0, + RAID_ACTION_IDLE, + RAID_ACTION_RESHAPE, + RAID_ACTION_FROZEN, + RAID_ACTION_RESYNC, + RAID_ACTION_CHECK, + RAID_ACTION_RECOVER, + RAID_ACTION_REPAIR +}; + +/** + */ +struct raid_device { + enum device_type type; + int device_num; + char *sysfs_path; + int raid_disks; + int degraded; + enum raid_state array_state; + enum raid_action sync_action; + enum raid_level level; +}; + +/** + */ +struct raid_device *raid_device_init(const char *path, unsigned int device_num, + enum device_type type); + +/** + */ +void raid_device_fini(struct raid_device *device); + +/** + */ +struct raid_device *raid_device_duplicate(struct raid_device *device); + +/** + */ +struct raid_device *find_raid_device(const struct list *raid_list, + char *raid_sysfs_path); +#endif /* _RAID_H_INCLUDED_ */ diff --git a/src/scsi.c b/src/scsi.c new file mode 100644 index 0000000..d3658c6 --- /dev/null +++ b/src/scsi.c @@ -0,0 +1,753 @@ +/* + * Intel(R) Enclosure LED Utilities + * Copyright (C) 2009-2019 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if _HAVE_DMALLOC_H +#include +#endif + +#include +#include + +#include "cntrl.h" +#include "config.h" +#include "enclosure.h" +#include "list.h" +#include "scsi.h" +#include "ses.h" +#include "status.h" +#include "sysfs.h" +#include "utils.h" + +static int debug = 0; + +static int get_ses_page(int fd, struct ses_page *p, int pg_code) +{ + int ret; + int retry_count = 3; + + do { + ret = sg_ll_receive_diag(fd, 1, pg_code, p->buf, sizeof(p->buf), + 0, debug); + } while (ret && retry_count--); + + if (!ret) + p->len = (p->buf[2] << 8) + p->buf[3] + 4; + return ret; +} + +static int process_page1(struct ses_pages *sp) +{ + int num_encl; /* number of subenclosures */ + unsigned char *ed; /* Enclosure Descriptor */ + int len = 0; + int sum_headers = 0; /* Number of Type descriptor headers */ + int i = 0; + + /* How many enclosures is in the main enclosure? */ + num_encl = sp->page1->buf[1] + 1; + /* Go to Enclosure Descriptor */ + ed = sp->page1->buf + 8; + for (i = 0; i < num_encl; i++, ed += len) { + if (ed + 3 > sp->page1->buf + sp->page1->len) { + log_debug + ("SES: Error, response pare 1 truncated at %d\n", + i); + return 1; + } + sum_headers += ed[2]; + len = ed[3] + 4; + if (len < 40) { + log_debug("SES: Response too short for page 1\n"); + continue; + } + } + + sp->page1_types = (struct type_descriptor_header *)ed; + sp->page1_types_len = sum_headers; + + /* ed is on type descr header */ + for (i = 0; i < sum_headers; i++, ed += 4) { + if (ed > sp->page1->buf + sp->page1->len) { + log_debug("SES: Response page 1 truncated at %d\n", i); + return 1; + } + } + return 0; +} + +static struct ses_pages *ses_init(void) +{ + struct ses_pages *sp; + sp = calloc(1, sizeof(*sp)); + if (!sp) + return NULL; + sp->page1 = calloc(1, sizeof(struct ses_page)); + if (!sp->page1) + goto sp1; + sp->page2 = calloc(1, sizeof(struct ses_page)); + if (!sp->page2) + goto sp2; + return sp; + sp2: + free(sp->page1); + sp1: + free(sp); + return NULL; +} + +static void ses_free(struct ses_pages *sp) +{ + if (!sp) + return; + free(sp->page1); + free(sp->page2); + free(sp->page10); + free(sp); +} + +static void dump_p10(unsigned char *p) +{ + int i; + printf("----------------------------------------------\n"); + for (i = 0; i < 8; i++, p += 16) { + printf("%p: %02x %02x %02x %02x %02x %02x %02x " \ + "%02x %02x %02x %02x %02x %02x %02x %02x %02x\n", (void *)p, + p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], + p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]); + } +} + +static int enclosure_open(const struct enclosure_device *enclosure) +{ + int fd = -1; + + if (enclosure->dev_path) + fd = open(enclosure->dev_path, O_RDWR); + + return fd; +} + +static int enclosure_load_pages(struct enclosure_device *enclosure) +{ + int ret; + int fd; + struct ses_pages *sp; + + if (enclosure->ses_pages) + return 0; + + fd = enclosure_open(enclosure); + if (fd == -1) + return 1; + + sp = ses_init(); + if (!sp) { + ret = 1; + goto end; + } + + /* Read configuration. */ + ret = get_ses_page(fd, sp->page1, ENCL_CFG_DIAG_STATUS); + if (ret) + goto end; + + ret = process_page1(sp); + if (ret) + goto end; + + /* Get Enclosure Status */ + ret = get_ses_page(fd, sp->page2, ENCL_CTRL_DIAG_STATUS); +end: + close(fd); + if (ret) + ses_free(sp); + else + enclosure->ses_pages = sp; + return ret; +} + +static int enclosure_load_page10(struct enclosure_device *enclosure) +{ + int ret; + int fd; + struct ses_page *p; + + if (enclosure->ses_pages && enclosure->ses_pages->page10) + return 0; + + ret = enclosure_load_pages(enclosure); + if (ret) + return ret; + + fd = enclosure_open(enclosure); + if (fd == -1) + return 1; + + p = calloc(1, sizeof(struct ses_page)); + if (!p) { + ret = 1; + goto end; + } + + /* Additional Element Status */ + ret = get_ses_page(fd, p, ENCL_ADDITIONAL_EL_STATUS); +end: + close(fd); + if (ret) + free(p); + else + enclosure->ses_pages->page10 = p; + + return ret; +} + +static void enclosure_free_pages(struct enclosure_device *enclosure) +{ + ses_free(enclosure->ses_pages); + enclosure->ses_pages = NULL; +} + +static void print_page10(struct ses_pages *sp) +{ + unsigned char *ai = sp->page10->buf + 8; + int i = 0, len = 0, eip = 0; + unsigned char *sas = NULL; + + while (ai < sp->page10->buf + sp->page10->len) { + printf("%s()[%d]: Inv: %d, EIP: %d, Proto: 0x%04x\n", __func__, + i++, ((ai[0] & 0x80) >> 7), ((ai[0] & 0x10) >> 4), + (unsigned int) (ai[0] & 0xf)); + printf("\tDescriptor len (x-1): %d\n", ai[1] + 1); + eip = ai[0] & 0x10; + if (eip) + printf("\tElement Index: %d\n", ai[3]); + len = ai[1] + 2; + if ((ai[0] & 0xf) == SCSI_PROTOCOL_SAS) { + if (eip) + sas = ai + 4; + else + sas = ai + 2; + printf("\tProtocol SAS:\n"); + printf("\tNumber of phy descriptors: %d\n", sas[0]); + printf("\tNot all phys: %d, descriptor type: 0x%1x\n", + (sas[1] & 1), ((sas[1] & 0xc0) >> 6)); + if (eip) { + printf("\tDevice slot number: %d\n", sas[3]); + sas += 2; + } + sas += 2; + printf("\tDevice type: 0x%01x\n", + (unsigned int)((sas[0] & 0x70) >> 4)); + printf("\tSMP Initiator Port: 0x%01x\n", + (unsigned int)((sas[2] & 2) >> 1)); + printf("\tSTP Initiator Port: 0x%01x\n", + (unsigned int)((sas[2] & 4) >> 2)); + printf("\tSSP Initiator Port: 0x%01x\n", + (unsigned int)((sas[2] & 8) >> 3)); + printf("\tSATA DEVICE: 0x%01x\n", + (unsigned int)(sas[3] & 1)); + printf("\tSMP Target Port: 0x%01x\n", + (unsigned int)((sas[3] & 2) >> 1)); + printf("\tSTP Target Port: 0x%01x\n", + (unsigned int)((sas[3] & 4) >> 2)); + printf("\tSSP Target Port: 0x%01x\n", + (unsigned int)((sas[3] & 8) >> 3)); + printf("\tSATA Port Selector: 0x%01x\n", + (unsigned int)((sas[3] & 0X80) >> 7)); + printf + ("\tAttached SAS Address: 0x%02x%02x%02x%02x%02x%02x%02x%02x\n", + sas[4], sas[5], sas[6], sas[7], sas[8], sas[9], + sas[10], sas[11]); + printf + ("\tSAS Address: 0x%02x%02x%02x%02x%02x%02x%02x%02x\n", + sas[12], sas[13], sas[14], sas[15], sas[16], + sas[17], sas[18], sas[19]); + printf("\tPHY Identified: 0x%01x\n", sas[20]); + } else + printf("\tProtocol not SAS: 0x%02x, skipping\n", + (unsigned int)(ai[0] & 0xf)); + /* */ + ai += len; + } + return; +} + +static enum ibpi_pattern ibpi_to_ses(enum ibpi_pattern ibpi) +{ + switch (ibpi) { + case IBPI_PATTERN_UNKNOWN: + case IBPI_PATTERN_ONESHOT_NORMAL: + case IBPI_PATTERN_NORMAL: + return SES_REQ_OK; + case IBPI_PATTERN_FAILED_ARRAY: + return SES_REQ_IFA; + case IBPI_PATTERN_DEGRADED: + return SES_REQ_ICA; + case IBPI_PATTERN_REBUILD: + return SES_REQ_REBUILD; + case IBPI_PATTERN_FAILED_DRIVE: + return SES_REQ_FAULT; + case IBPI_PATTERN_LOCATE: + return SES_REQ_IDENT; + case IBPI_PATTERN_HOTSPARE: + return SES_REQ_HOSTSPARE; + case IBPI_PATTERN_PFA: + return SES_REQ_PRDFAIL; + default: + return ibpi; + } +} + +static int ses_set_message(enum ibpi_pattern ibpi, struct ses_slot_ctrl_elem *el) +{ + struct ses_slot_ctrl_elem msg; + + memset(&msg, 0, sizeof(msg)); + if (ibpi == IBPI_PATTERN_LOCATE_OFF) { + /* + * For locate_off we don't set a new state, just clear the + * IDENT bit and the bits that are reserved or have different + * meanings in Status and Control pages (RQST ACTIVE and + * RQST MISSING). + */ + _clr_ident(el->b); + el->b2 &= 0x4e; + el->b3 &= 0x3c; + return 0; + } + + switch (ibpi_to_ses(ibpi)) { + case SES_REQ_ABORT: + _set_abrt(msg.b); + break; + case SES_REQ_REBUILD: + _set_rebuild(msg.b); + break; + case SES_REQ_IFA: + _set_ifa(msg.b); + break; + case SES_REQ_ICA: + _set_ica(msg.b); + break; + case SES_REQ_CONS_CHECK: + _set_cons_check(msg.b); + break; + case SES_REQ_HOSTSPARE: + _set_hspare(msg.b); + break; + case SES_REQ_RSVD_DEV: + _set_rsvd_dev(msg.b); + break; + case SES_REQ_OK: + _set_ok(msg.b); + break; + case SES_REQ_IDENT: + _set_ident(msg.b); + break; + case SES_REQ_RM: + _set_rm(msg.b); + break; + case SES_REQ_INS: + _set_ins(msg.b); + break; + case SES_REQ_MISSING: + _set_miss(msg.b); + break; + case SES_REQ_DNR: + _set_dnr(msg.b); + break; + case SES_REQ_ACTIVE: + _set_actv(msg.b); + break; + case SES_REQ_EN_BB: + _set_enbb(msg.b); + break; + case SES_REQ_EN_BA: + _set_enba(msg.b); + break; + case SES_REQ_DEV_OFF: + _set_off(msg.b); + break; + case SES_REQ_FAULT: + _set_fault(msg.b); + break; + case SES_REQ_PRDFAIL: + _set_prdfail(msg.b); + break; + default: + return 1; + } + + *el = msg; + + return 0; +} + +static int ses_write_msg(enum ibpi_pattern ibpi, struct block_device *device) +{ + struct ses_pages *sp = device->enclosure->ses_pages; + int idx = device->encl_index; + /* Move do descriptors */ + struct ses_slot_ctrl_elem *descriptors = (void *)(sp->page2->buf + 8); + int i; + struct ses_slot_ctrl_elem *desc_element = NULL; + element_type local_element_type = SES_UNSPECIFIED; + + for (i = 0; i < sp->page1_types_len; i++) { + struct type_descriptor_header *t = &sp->page1_types[i]; + + descriptors++; /* At first, skip overall header. */ + + if (t->element_type == SES_DEVICE_SLOT || + t->element_type == SES_ARRAY_DEVICE_SLOT) { + if (local_element_type < t->element_type && + t->num_of_elements > idx) { + local_element_type = t->element_type; + desc_element = &descriptors[idx]; + } + } else { + /* + * Device Slot and Array Device Slot elements are + * always first on the type descriptor header list + */ + break; + } + + descriptors += t->num_of_elements; + } + + if (desc_element) { + int ret = ses_set_message(ibpi, desc_element); + if (ret) + return ret; + /* keep PRDFAIL, clear rest */ + desc_element->common_control &= 0x40; + /* set select */ + desc_element->common_control |= 0x80; + + /* second byte is valid only for Array Device Slot */ + if (local_element_type != SES_ARRAY_DEVICE_SLOT) + desc_element->array_slot_control = 0; + + return 0; + } + + return 1; +} + +static int ses_send_diag(struct enclosure_device *enclosure) +{ + int ret; + int fd; + + fd = enclosure_open(enclosure); + if (fd == -1) + return 1; + + ret = sg_ll_send_diag(fd, 0, 1, 0, 0, 0, 0, + enclosure->ses_pages->page2->buf, + enclosure->ses_pages->page2->len, + 0, debug); + close(fd); + return ret; +} + +static char *get_drive_end_dev(const char *path) +{ + char *s, *c, *p; + + c = strstr(path, "end_device"); + if (!c) + return NULL; + s = strchr(c, '/'); + if (!s) + return NULL; + p = calloc(s - c + 1, sizeof(*p)); + if (!p) + return NULL; + + strncpy(p, c, s - c); + return p; +} + +static uint64_t get_drive_sas_addr(const char *path) +{ + uint64_t ret = 0; + size_t size = strlen(path) * 2; + char *buff, *end_dev; + + /* Make big buffer. */ + buff = malloc(size + 1); + if (!buff) + return ret; + + end_dev = get_drive_end_dev(path); + if (!end_dev) { + free(buff); + return ret; + } + + snprintf(buff, size, "/sys/class/sas_end_device/%s/device/sas_device/%s", + end_dev, end_dev); + + ret = get_uint64(buff, ret, "sas_address"); + + free(end_dev); + free(buff); + + return ret; +} + +static int get_encl_slot(struct block_device *device) +{ + struct ses_pages *sp; + unsigned char *add_desc = NULL; + unsigned char *ap = NULL, *addr_p = NULL; + int i, j, len = 0; + uint64_t addr, addr_cmp; + int idx; + + /* try to get slot from sysfs */ + idx = get_int(device->cntrl_path, -1, "slot"); + if (idx != -1) + return idx; + + /* + * Older kernels may not have the "slot" sysfs attribute, + * fallback to Page10 method. + */ + if (enclosure_load_page10(device->enclosure)) + return -1; + sp = device->enclosure->ses_pages; + + addr = get_drive_sas_addr(device->sysfs_path); + if (!addr) + return -1; + + if (debug) + print_page10(sp); + + /* Check Page10 for address. Extract index. */ + ap = add_desc = sp->page10->buf + 8; + for (i = 0; i < sp->page1_types_len; i++) { + struct type_descriptor_header *t = &sp->page1_types[i]; + + if (t->element_type == SES_DEVICE_SLOT || + t->element_type == SES_ARRAY_DEVICE_SLOT) { + for (j = 0; j < t->num_of_elements; j++, ap += len) { + if (debug) + dump_p10(ap); + /* Get Additional Element Status Descriptor */ + /* length (x-1) */ + len = ap[1] + 2; + if ((ap[0] & 0xf) != SCSI_PROTOCOL_SAS) + continue; /* need SAS PROTO */ + /* It is a SAS protocol, go on */ + if ((ap[0] & 0x10)) /* Check EIP */ + addr_p = ap + 8; + else + addr_p = ap + 4; + /* Process only PHY 0 descriptor. */ + + /* Convert be64 to le64 */ + addr_cmp = ((uint64_t)addr_p[12] << 8*7) | + ((uint64_t)addr_p[13] << 8*6) | + ((uint64_t)addr_p[14] << 8*5) | + ((uint64_t)addr_p[15] << 8*4) | + ((uint64_t)addr_p[16] << 8*3) | + ((uint64_t)addr_p[17] << 8*2) | + ((uint64_t)addr_p[18] << 8*1) | + ((uint64_t)addr_p[19]); + + if (addr == addr_cmp) { + idx = ap[0] & 0x10 ? ap[3] : j; + return idx; + } + } + } else { + /* + * Device Slot and Array Device Slot elements are + * always first on the type descriptor header list + */ + break; + } + } + return -1; +} + +/** + */ +static int _slot_match(const char *slot_path, const char *device_path) +{ + char temp[PATH_MAX], link[PATH_MAX]; + + snprintf(temp, sizeof(temp), "%s/device", slot_path); + + if (realpath(temp, link) == NULL) + return 0; + + return strncmp(link, device_path, strlen(link)) == 0; +} + +/** + */ +static char *_slot_find(const char *enclo_path, const char *device_path) +{ + struct list dir; + char *temp, *result = NULL; + + if (scan_dir(enclo_path, &dir) == 0) { + list_for_each(&dir, temp) { + if (_slot_match(temp, device_path)) { + result = str_dup(temp); + break; + } + } + list_erase(&dir); + } + return result; +} + +int scsi_get_enclosure(struct block_device *device) +{ + struct enclosure_device *encl; + + if (!device || !device->sysfs_path) + return 0; + + list_for_each(sysfs_get_enclosure_devices(), encl) { + if (_slot_match(encl->sysfs_path, device->cntrl_path)) { + device->enclosure = encl; + device->encl_index = get_encl_slot(device); + break; + } + } + + return (device->enclosure != NULL && device->encl_index != -1); +} + +/** + */ +int scsi_ses_write(struct block_device *device, enum ibpi_pattern ibpi) +{ + int ret; + + if (!device || !device->sysfs_path || !device->enclosure || + device->encl_index == -1) + __set_errno_and_return(EINVAL); + + /* write only if state has changed */ + if (ibpi == device->ibpi_prev) + return 1; + + if ((ibpi < IBPI_PATTERN_NORMAL) || (ibpi > SES_REQ_FAULT)) + __set_errno_and_return(ERANGE); + + ret = enclosure_load_pages(device->enclosure); + if (ret) { + log_warning + ("Unable to send %s message to %s. Device is missing?", + ibpi2str(ibpi), strstr(device->sysfs_path, "host")); + return ret; + } + + return ses_write_msg(ibpi, device); +} + +int scsi_ses_flush(struct block_device *device) +{ + int ret; + + if (!device || !device->enclosure) + return 1; + + if (!device->enclosure->ses_pages) + return 0; + + ret = ses_send_diag(device->enclosure); + + enclosure_free_pages(device->enclosure); + return ret; +} + +/** + * @brief Gets a path to slot of sas controller. + * + * This function returns a sysfs path to component of enclosure the device + * belongs to. + * + * @param[in] path Canonical sysfs path to block device. + * + * @return A sysfs path to controller device associated with the given + * block device if successful, otherwise NULL pointer. + */ +static char *sas_get_slot_path(const char *path, const char *ctrl_path) +{ + char *host; + char host_path[PATH_MAX] = { 0 }; + size_t ctrl_path_len = strlen(ctrl_path); + + if (strncmp(path, ctrl_path, ctrl_path_len) != 0) + return NULL; + host = get_path_hostN(path); + if (host) { + snprintf(host_path, sizeof(host_path), "%s/%s/bsg/sas_%s", + ctrl_path, host, host); + free(host); + } + return str_dup(host_path); +} + +/** + */ +static char *_get_enc_slot_path(const char *path) +{ + struct enclosure_device *device; + char *result = NULL; + + list_for_each(sysfs_get_enclosure_devices(), device) { + result = _slot_find(device->sysfs_path, path); + if (result != NULL) + break; + } + return result; +} + +/** + */ +char *scsi_get_slot_path(const char *path, const char *ctrl_path) +{ + char *result = NULL; + + result = _get_enc_slot_path(path); + if (!result) + result = sas_get_slot_path(path, ctrl_path); + return result; +} diff --git a/src/scsi.h b/src/scsi.h new file mode 100644 index 0000000..7f1d384 --- /dev/null +++ b/src/scsi.h @@ -0,0 +1,71 @@ +/* + * Intel(R) Enclosure LED Utilities + * Copyright (C) 2009-2018 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#ifndef _SCSI_H_INCLUDED +#define _SCSI_H_INCLUDED + +#include "block.h" +#include "ibpi.h" + +/** + * @brief Gets a path to slot of an enclosure. + * + * This function returns a sysfs path to component of enclosure the device + * belongs to. + * + * @param[in] path Canonical sysfs path to block device. + * + * @return A sysfs path to enclosure's component associated with the given + * block device if successful, otherwise NULL pointer. + */ +char *scsi_get_slot_path(const char *path, const char *cntrl_path); + +/** + * @brief Prepares SES message based on ibpi pattern. + * + * @param[in] device Sysfs path of a drive in enclosure. + * @param[in] ibpi IBPI pattern to visualize. + * + * @return 0 on success, otherwise error. + */ +int scsi_ses_write(struct block_device *device, enum ibpi_pattern ibpi); + +/** + * @brief Sends message to SES processor of an enclosure. + * + * This function send a message to an enclosure in order to control LEDs of + * the given slot/component. It uses interface of ENCLOSURE kernel module to + * control LEDs. + * + * @param[in] device Sysfs path of a drive in enclosure. + * + * @return 0 on success, otherwise error. + */ +int scsi_ses_flush(struct block_device *device); + +/** + * @brief Assigns enclosure device to block device. + * + * @param[in] device Path to block device. + * + * @return 1 on success, 0 otherwise. + * */ +int scsi_get_enclosure(struct block_device *device); + +#endif /* _SCSI_H_INCLUDED_ */ diff --git a/src/ses.h b/src/ses.h new file mode 100644 index 0000000..d0c4227 --- /dev/null +++ b/src/ses.h @@ -0,0 +1,173 @@ +/* + * Intel(R) Enclosure LED Utilities + * Copyright (C) 2009-2017 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#ifndef _SES_H_INCLUDED_ +#define _SES_H_INCLUDED_ + +#include + +/* Size of buffer for SES-2 Messages. */ +#define SES_ALLOC_BUFF 4096 + +#define ENCL_CFG_DIAG_STATUS 0x01 +#define ENCL_CTRL_DIAG_STATUS 0x02 +#define ENCL_CTRL_DIAG_CFG 0x02 +#define ENCL_EL_DESCR_STATUS 0x07 +#define ENCL_ADDITIONAL_EL_STATUS 0x0a +#define SCSI_PROTOCOL_SAS 6 + +typedef enum __attribute__((packed)) { + SES_UNSPECIFIED = 0x00, + SES_DEVICE_SLOT = 0x01, + SES_ARRAY_DEVICE_SLOT = 0x17, +} element_type; + +static inline void _set_prdfail(unsigned char *u) +{ + u[0] |= (1 << 6); +} + +static inline void _set_abrt(unsigned char *u) +{ + u[1] |= (1 << 0); +} + +static inline void _set_rebuild(unsigned char *u) +{ + u[1] |= (1 << 1); +} + +static inline void _set_ifa(unsigned char *u) +{ + u[1] |= (1 << 2); +} + +static inline void _set_ica(unsigned char *u) +{ + u[1] |= (1 << 3); +} + +static inline void _set_cons_check(unsigned char *u) +{ + u[1] |= (1 << 4); +} + +static inline void _set_hspare(unsigned char *u) +{ + u[1] |= (1 << 5); +} + +static inline void _set_rsvd_dev(unsigned char *u) +{ + u[1] |= (1 << 6); +} + +static inline void _set_ok(unsigned char *u) +{ + u[1] |= (1 << 7); +} + +static inline void _set_ident(unsigned char *u) +{ + u[2] |= (1 << 1); +} + +static inline void _clr_ident(unsigned char *u) +{ + u[2] &= ~(1 << 1); +} + +static inline void _set_rm(unsigned char *u) +{ + u[2] |= (1 << 2); +} + +static inline void _set_ins(unsigned char *u) +{ + u[2] |= (1 << 3); +} + +static inline void _set_miss(unsigned char *u) +{ + u[2] |= (1 << 4); +} + +static inline void _set_dnr(unsigned char *u) +{ + u[2] |= (1 << 6); +} + +static inline void _set_actv(unsigned char *u) +{ + u[2] |= (1 << 7); +} + +static inline void _set_enbb(unsigned char *u) +{ + u[3] |= (1 << 2); +} + +static inline void _set_enba(unsigned char *u) +{ + u[3] |= (1 << 3); +} + +static inline void _set_off(unsigned char *u) +{ + u[3] |= (1 << 4); +} + +static inline void _set_fault(unsigned char *u) +{ + u[3] |= (1 << 5); +} + +struct ses_page { + unsigned char buf[SES_ALLOC_BUFF]; + int len; +}; + +struct type_descriptor_header { + element_type element_type; + __u8 num_of_elements; + __u8 subenclosure_id; + __u8 type_desc_text_len; +}; + +struct ses_pages { + struct ses_page *page1; + struct ses_page *page2; + struct ses_page *page10; + struct type_descriptor_header *page1_types; + int page1_types_len; +}; + +struct ses_slot_ctrl_elem { + union { + struct { + __u8 common_control; + __u8 array_slot_control; + __u8 b2; + __u8 b3; + }; + __u8 b[4]; + }; +}; + +#endif diff --git a/src/slave.c b/src/slave.c new file mode 100644 index 0000000..951da06 --- /dev/null +++ b/src/slave.c @@ -0,0 +1,149 @@ +/* + * Intel(R) Enclosure LED Utilities + * Copyright (C) 2009-2019 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#include +#include +#include +#include +#include +#include + +#if _HAVE_DMALLOC_H +#include +#endif + +#include "config.h" +#include "ibpi.h" +#include "list.h" +#include "slave.h" +#include "status.h" +#include "sysfs.h" +#include "utils.h" + +/** + */ +static unsigned char _get_state(const char *path) +{ + char *p, *t, *s; + unsigned char result = SLAVE_STATE_UNKNOWN; + + s = p = get_text(path, "state"); + if (p) { + while (s) { + t = strchr(s, ','); + if (t) + *(t++) = '\0'; + if (strcmp(s, "spare") == 0) + result |= SLAVE_STATE_SPARE; + else if (strcmp(s, "in_sync") == 0) + result |= SLAVE_STATE_IN_SYNC; + else if (strcmp(s, "faulty") == 0) + result |= SLAVE_STATE_FAULTY; + else if (strcmp(s, "write_mostly") == 0) + result |= SLAVE_STATE_WRITE_MOSTLY; + else if (strcmp(s, "blocked") == 0) + result |= SLAVE_STATE_BLOCKED; + s = t; + } + free(p); + } + return result; +} + +/** + */ +static unsigned int _get_errors(const char *path) +{ + return get_int(path, 0, "errors"); +} + +/** + */ +static unsigned int _get_slot(const char *path) +{ + unsigned int result = -1; + + char *p = get_text(path, "slot"); + if (p) { + if (strcmp(p, "none") != 0) + result = atoi(p); + free(p); + } + return result; +} + +/** + */ +static struct block_device *_get_block(const char *path, struct list *block_list) +{ + char temp[PATH_MAX]; + char link[PATH_MAX]; + struct block_device *device; + + snprintf(temp, sizeof(temp), "%s/block", path); + + if (!realpath(temp, link)) + return NULL; + + /* translate partition to master block dev */ + if (snprintf(temp, PATH_MAX, "%s/partition", link) > 0) { + struct stat sb; + char *ptr; + + if (stat(temp, &sb) == 0 && S_ISREG(sb.st_mode)) { + ptr = strrchr(link, '/'); + if (ptr) + *ptr = '\0'; + } + } + + list_for_each(block_list, device) { + if (strcmp(device->sysfs_path, link) == 0) + return device; + } + return NULL; +} + +/** + */ +struct slave_device *slave_device_init(const char *path, struct list *block_list) +{ + struct slave_device *device = NULL; + struct block_device *block; + + block = _get_block(path, block_list); + if (block) { + device = malloc(sizeof(struct slave_device)); + if (device) { + device->raid = NULL; + device->state = _get_state(path); + device->slot = _get_slot(path); + device->errors = _get_errors(path); + device->block = block; + } + } + return device; +} + +/** + */ +void slave_device_fini(struct slave_device *device) +{ + free(device); +} diff --git a/src/slave.h b/src/slave.h new file mode 100644 index 0000000..a22deb5 --- /dev/null +++ b/src/slave.h @@ -0,0 +1,53 @@ +/* + * Intel(R) Enclosure LED Utilities + * Copyright (C) 2009-2018 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#ifndef _SLAVE_H_INCLUDED_ +#define _SLAVE_H_INCLUDED_ + +#include "block.h" +#include "raid.h" + +/** + */ +#define SLAVE_STATE_UNKNOWN 0x00 +#define SLAVE_STATE_IN_SYNC 0x01 +#define SLAVE_STATE_SPARE 0x02 +#define SLAVE_STATE_FAULTY 0x04 +#define SLAVE_STATE_WRITE_MOSTLY 0x08 +#define SLAVE_STATE_BLOCKED 0x10 + +/** + */ +struct slave_device { + struct raid_device *raid; + unsigned int errors; + unsigned int slot; + struct block_device *block; + unsigned char state; +}; + +/** + */ +struct slave_device *slave_device_init(const char *path, struct list *block_list); + +/** + */ +void slave_device_fini(struct slave_device *device); + +#endif /* _SLAVE_H_INCLUDED_ */ diff --git a/src/smp.c b/src/smp.c new file mode 100644 index 0000000..2c089f8 --- /dev/null +++ b/src/smp.c @@ -0,0 +1,619 @@ +/* + * Intel(R) Enclosure LED Utilities + * Copyright (C) 2011-2019 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if _HAVE_DMALLOC_H +#include +#endif + +#include "block.h" +#include "cntrl.h" +#include "config.h" +#include "enclosure.h" +#include "ibpi.h" +#include "list.h" +#include "scsi.h" +#include "smp.h" +#include "status.h" +#include "sysfs.h" +#include "utils.h" + +#define GPIO_TX_GP1 0x01 + +#define INIT_IBPI(act, loc, err) \ + { .error = err, \ + .locate = loc, \ + .activity = act \ + } + +#define LED_OFF 0 +#define LED_ON 1 +#define LED_4HZ 2 +#define LED_I4HZ 3 +#define LED_EOF 4 +#define LED_SOF 5 +#define LED_2HZ 6 +#define LED_I2HZ 7 + +static const struct gpio_rx_table { + struct gpio_tx_register_byte pattern; + int support_mask; +} ibpi2sgpio[] = { + [IBPI_PATTERN_UNKNOWN] = { INIT_IBPI(LED_SOF,LED_OFF,LED_OFF), 1 }, /* OK */ + [IBPI_PATTERN_ONESHOT_NORMAL] = { INIT_IBPI(LED_SOF,LED_OFF,LED_OFF), 1 }, /* OK */ + [IBPI_PATTERN_NORMAL] = { INIT_IBPI(LED_SOF,LED_OFF,LED_OFF), 1 }, /* OK */ + [IBPI_PATTERN_DEGRADED] = { INIT_IBPI(LED_SOF,LED_OFF,LED_OFF), 0 }, /* NO */ + [IBPI_PATTERN_REBUILD] = { INIT_IBPI(LED_SOF,LED_ON,LED_ON), 1 }, /* OK */ + [IBPI_PATTERN_FAILED_ARRAY] = { INIT_IBPI(LED_SOF,LED_4HZ,LED_OFF), 0 }, /* NO */ + [IBPI_PATTERN_HOTSPARE] = { INIT_IBPI(LED_SOF,LED_OFF,LED_4HZ), 0 }, /* NO */ + [IBPI_PATTERN_PFA] = { INIT_IBPI(LED_SOF,LED_OFF,LED_2HZ), 0 }, /* NO */ + [IBPI_PATTERN_FAILED_DRIVE] = { INIT_IBPI(LED_SOF,LED_OFF,LED_ON), 1 }, /* OK */ + [IBPI_PATTERN_LOCATE] = { INIT_IBPI(LED_SOF,LED_ON,LED_OFF), 1 }, /* OK */ + [IBPI_PATTERN_LOCATE_OFF] = { INIT_IBPI(LED_SOF,LED_OFF,LED_OFF), 1 } /* OK */ +}; + +struct smp_read_response_frame_header { + uint8_t frame_type; /* =0x41 */ + uint8_t function; /* =0x02 for read, 0x82 for write */ + uint8_t function_result; + uint8_t reserved; + uint32_t read_data[]; /* variable length of data */ + /* uint32_t crc; */ +} __attribute__ ((__packed__)); + +struct smp_write_response_frame { + uint8_t frame_type; /* =0x41 */ + uint8_t function; /* =0x02 for read, 0x82 for write */ + uint8_t function_result; + uint8_t reserved; + uint32_t crc; +} __attribute__ ((__packed__)); + +struct smp_read_request_frame { + uint8_t frame_type; /* =0x40 */ + uint8_t function; /* =0x02 for read, 0x82 for write */ + uint8_t register_type; + uint8_t register_index; + uint8_t register_count; + uint8_t reserved[3]; + uint32_t crc; +} __attribute__ ((__packed__)); + +struct smp_write_request_frame_header { + uint8_t frame_type; /* =0x40 */ + uint8_t function; /* =0x02 for read, 0x82 for write */ + uint8_t register_type; + uint8_t register_index; + uint8_t register_count; + uint8_t reserved[3]; + uint32_t data[]; /* variable length of data */ + /* uint32_t crc; */ +} __attribute__ ((__packed__)); + +/** + * to_sas_gpio_gp_bit - given the gpio frame data find the byte/bit position of 'od' + * @od: od bit to find + * @data: incoming bitstream (from frame) + * @index: requested data register index (from frame) + * @count: total number of registers in the bitstream (from frame) + * @bit: bit position of 'od' in the returned byte + * + * returns NULL if 'od' is not in 'data' + * + * From SFF-8485 v0.7: + * "In GPIO_TX[1], bit 0 of byte 3 contains the first bit (i.e., OD0.0) + * and bit 7 of byte 0 contains the 32nd bit (i.e., OD10.1). + * + * In GPIO_TX[2], bit 0 of byte 3 contains the 33rd bit (i.e., OD10.2) + * and bit 7 of byte 0 contains the 64th bit (i.e., OD21.0)." + * + * The general-purpose (raw-bitstream) RX registers have the same layout + * although 'od' is renamed 'id' for 'input data'. + * + * SFF-8489 defines the behavior of the LEDs in response to the 'od' values. + */ +static unsigned char *to_sas_gpio_gp_bit(unsigned int od, unsigned char *data, + unsigned char index, + unsigned char count, + unsigned char *bit) +{ + unsigned int reg; + unsigned char byte; + + /* gp registers start at index 1 */ + if (index == 0) + return NULL; + + index--; /* make index 0-based */ + if (od < index * 32) + return NULL; + + od -= index * 32; + reg = od >> 5; + + if (reg >= count) + return NULL; + + od &= (1 << 5) - 1; + byte = 3 - (od >> 3); + *bit = od & ((1 << 3) - 1); + + return &data[reg * 4 + byte]; +} + +int try_test_sas_gpio_gp_bit(unsigned int od, unsigned char *data, + unsigned char index, unsigned char count) +{ + unsigned char *byte; + unsigned char bit; + + byte = to_sas_gpio_gp_bit(od, data, index, count, &bit); + if (!byte) + return -1; + + return (*byte >> bit) & 1; +} + +int try_set_sas_gpio_gp_bit(unsigned int od, unsigned char *data, + unsigned char index, unsigned char count) +{ + unsigned char *byte; + unsigned char bit; + + byte = to_sas_gpio_gp_bit(od, data, index, count, &bit); + if (!byte) + return 0; + + *byte |= 1 << bit; + return 1; +} + +int try_clear_sas_gpio_gp_bit(unsigned int od, unsigned char *data, + unsigned char index, unsigned char count) +{ + unsigned char *byte; + unsigned char bit; + + byte = to_sas_gpio_gp_bit(od, data, index, count, &bit); + if (!byte) + return 0; + + *byte &= ~(1 << bit); + return 1; +} + +/** + * set_raw_pattern - turn a tx register into a tx_gp bitstream + * + * takes @dev_idx (phy index) and a @pattern (error, locate, activity) + * tuple and modifies the bitstream in @data accordingly */ +int set_raw_pattern(unsigned int dev_idx, unsigned char *data, + const struct gpio_tx_register_byte *pattern) +{ + int od_offset = dev_idx * 3; + int rc = 0; + + if (pattern->activity == LED_ON) + rc += + try_set_sas_gpio_gp_bit(od_offset + 0, data, GPIO_TX_GP1, + 1); + else + rc += + try_clear_sas_gpio_gp_bit(od_offset + 0, data, GPIO_TX_GP1, + 1); + + if (pattern->locate == LED_ON) + rc += + try_set_sas_gpio_gp_bit(od_offset + 1, data, GPIO_TX_GP1, + 1); + else + rc += + try_clear_sas_gpio_gp_bit(od_offset + 1, data, GPIO_TX_GP1, + 1); + + if (pattern->error == LED_ON) + rc += + try_set_sas_gpio_gp_bit(od_offset + 2, data, GPIO_TX_GP1, + 1); + else + rc += + try_clear_sas_gpio_gp_bit(od_offset + 2, data, GPIO_TX_GP1, + 1); + + return rc == 3; +} + +/** + * @brief open device for smp protocol + */ +static int _open_smp_device(const char *filename) +{ + char buf[PATH_MAX]; + FILE *df; + int hba_fd; + int dmaj, dmin; + snprintf(buf, sizeof(buf), "%s/dev", filename); + df = fopen(buf, "r"); + if (!df) + return -1; + if (fgets(buf, sizeof(buf), df) == NULL) { + fclose(df); + return -1; + } + if (sscanf(buf, "%d:%d", &dmaj, &dmin) != 2) { + fclose(df); + return -1; + } + fclose(df); + snprintf(buf, sizeof(buf), "/var/tmp/led.%d.%d.%d", dmaj, dmin, + getpid()); + if (mknod(buf, S_IFCHR | S_IRUSR | S_IWUSR, makedev(dmaj, dmin)) < 0) + return -1; + hba_fd = open(buf, O_RDWR); + unlink(buf); + if (hba_fd < 0) + return -1; + return hba_fd; +} + +/** + * @brief close smp device + */ +static int _close_smp_device(int fd) +{ + return close(fd); +} + +/** + @brief use sg protocol in order to send data directly to hba driver + */ +static int _send_smp_frame(int hba, void *data, size_t data_size, + void *response, size_t *response_size) +{ + struct sg_io_v4 sg_frame; + uint8_t request_buf[SCSI_MAX_CDB_LENGTH]; + int response_status = 0; + + /* wrap the frame into sg structure */ + memset(&sg_frame, 0, sizeof(sg_frame)); + sg_frame.guard = 'Q'; + sg_frame.protocol = BSG_PROTOCOL_SCSI; + sg_frame.subprotocol = BSG_SUB_PROTOCOL_SCSI_TRANSPORT; + + sg_frame.request_len = sizeof(request_buf); + sg_frame.request = (uintptr_t) request_buf; + + sg_frame.dout_xfer_len = data_size; + sg_frame.dout_xferp = (uintptr_t) data; + + sg_frame.din_xfer_len = *response_size; + sg_frame.din_xferp = (uintptr_t) response; + + sg_frame.timeout = SG_RESPONSE_TIMEOUT; + /* send ioctl */ + if (ioctl(hba, SG_IO, &sg_frame) < 0) + return -1; + + /* return status */ + if (sg_frame.driver_status) + response_status = sg_frame.driver_status; + else if (sg_frame.transport_status) + response_status = sg_frame.transport_status; + else if (sg_frame.device_status) + response_status = sg_frame.device_status; + + *response_size = sg_frame.din_xfer_len - sg_frame.din_resid; + + return response_status; +} + +/* 1024 bytes for data, 4 for crc */ +#define MAX_SMP_FRAME_DATA 1024 +#define MAX_SMP_FRAME_LEN (sizeof(struct smp_write_request_frame_header) + \ + MAX_SMP_FRAME_DATA + SMP_FRAME_CRC_LEN) + +/** + @brief prepare full smp frame ready to send to hba + + @note len is a number of 32bit words + */ +static int _start_smp_write_gpio(int hba, + struct smp_write_request_frame_header *header, + void *data, size_t len) +{ + uint8_t buf[MAX_SMP_FRAME_LEN]; + struct smp_write_response_frame response; + size_t response_size = sizeof(response); + int status; + + memset(&response, 0, sizeof(response)); + /* create full frame */ + if (len * SMP_DATA_CHUNK_SIZE > MAX_SMP_FRAME_DATA) + __set_errno_and_return(EINVAL); + memset(buf, 0, sizeof(buf)); + memcpy(buf, header, sizeof(*header)); + memcpy(buf + sizeof(*header), data, len * SMP_DATA_CHUNK_SIZE); + + status = + _send_smp_frame(hba, buf, + sizeof(*header) + len * SMP_DATA_CHUNK_SIZE + + SMP_FRAME_CRC_LEN, &response, &response_size); + + /* if frame is somehow malformed return failure */ + if (status != GPIO_STATUS_OK || + response.frame_type != SMP_FRAME_TYPE_RESP || + response.function != header->function) { + return GPIO_STATUS_FAILURE; + } + + return response.function_result; +} + +/** + @brief prepare smp frame header + */ +int smp_write_gpio(const char *path, int smp_reg_type, + int smp_reg_index, int smp_reg_count, void *data, + size_t len) +{ + struct smp_write_request_frame_header header; + int status; + header.frame_type = SMP_FRAME_TYPE_REQ; + header.function = SMP_FUNC_GPIO_WRITE; + header.register_type = smp_reg_type; + header.register_index = smp_reg_index; + header.register_count = smp_reg_count; + memset(header.reserved, 0, sizeof(header.reserved)); + int fd = _open_smp_device(path); + status = _start_smp_write_gpio(fd, &header, data, len); + _close_smp_device(fd); + return status; +} + +#define BLINK_GEN_1HZ 8 +#define BLINK_GEN_2HZ 4 +#define BLINK_GEN_4HZ 2 +#define DEFAULT_FORCED_ACTIVITY_OFF 1 +#define DEFAULT_MAXIMUM_ACTIVITY_ON 2 +#define DEFAULT_STRETCH_ACTIVITY_OFF 0 +#define DEFAULT_STRETCH_ACTIVITY_ON 0 + +/* one data chunk is 32bit long */ +#define SMP_DATA_CHUNKS 1 + +struct gpio_tx_register_byte *get_bdev_ibpi_buffer(struct block_device *bdevice) +{ + if (bdevice && bdevice->host) + return bdevice->host->ibpi_state_buffer; + return NULL; +} + +/** + */ +int scsi_smp_fill_buffer(struct block_device *device, enum ibpi_pattern ibpi) +{ + const char *sysfs_path = device->cntrl_path; + struct gpio_tx_register_byte *gpio_tx; + + if (sysfs_path == NULL) + __set_errno_and_return(EINVAL); + if ((ibpi < IBPI_PATTERN_NORMAL) || (ibpi > IBPI_PATTERN_LOCATE_OFF)) + __set_errno_and_return(ERANGE); + if (!device->cntrl) { + log_debug("No ctrl dev for '%s'", strstr(sysfs_path, "host")); + __set_errno_and_return(ENODEV); + } + if (device->cntrl->cntrl_type != CNTRL_TYPE_SCSI) { + log_debug("No SCSI ctrl dev '%s'", strstr(sysfs_path, "host")); + __set_errno_and_return(EINVAL); + } + if (!device->host) { + log_debug("No host for '%s'", strstr(sysfs_path, "host")); + __set_errno_and_return(ENODEV); + } + + if (device->cntrl->isci_present && !ibpi2sgpio[ibpi].support_mask) { + char *c = strrchr(device->sysfs_path, '/'); + if (c++) { + log_debug + ("pattern %s not supported for device (/dev/%s)", + ibpi2str(ibpi), c); + fprintf(stderr, + "%s(): pattern %s not supported for device (/dev/%s)\n", + __func__, ibpi2str(ibpi), c); + } else { + log_debug("pattern %s not supported for device %s", + ibpi2str(ibpi), device->sysfs_path); + fprintf(stderr, + "%s(): pattern %s not supported for device\n\t(%s)\n", + __func__, ibpi2str(ibpi), device->sysfs_path); + } + __set_errno_and_return(ENOTSUP); + } + + gpio_tx = get_bdev_ibpi_buffer(device); + if (!gpio_tx) { + log_debug("%s(): no IBPI buffer. Skipping.", __func__); + __set_errno_and_return(ENODEV); + } + + if (device->cntrl->isci_present) { + /* update bit stream for this device */ + set_raw_pattern(device->phy_index, + &device->host->bitstream[0], &ibpi2sgpio[ibpi].pattern); + } else { + /* + * GPIO_TX[n] register has the highest numbered drive of the + * four in the first byte and the lowest numbered drive in the + * fourth byte. See SFF-8485 Rev. 0.7 Table 24. + */ + gpio_tx[device->phy_index + 3 - (device->phy_index % 4) * 2] = + ibpi2sgpio[ibpi].pattern; + } + + /* write only if state has changed */ + if (ibpi != device->ibpi_prev) + device->host->flush = 1; + + return 1; +} + +int scsi_smp_write_buffer(struct block_device *device) +{ + const char *sysfs_path = device->cntrl_path; + + if (sysfs_path == NULL) + __set_errno_and_return(EINVAL); + if (!device->host) + __set_errno_and_return(ENODEV); + + if (device->host->flush) { + device->host->flush = 0; + /* re-transmit the bitstream */ + if (device->cntrl->isci_present) { + return smp_write_gpio(sysfs_path, + GPIO_REG_TYPE_TX_GP, + GPIO_TX_GP1, 1, + &device->host->bitstream[0], + SMP_DATA_CHUNKS); + } else { + return smp_write_gpio(sysfs_path, + GPIO_REG_TYPE_TX, + 0, (device->host->ports+3)/4, + device->host->ibpi_state_buffer, + (device->host->ports+3)/4); + } + } else + return 1; +} + +/** + */ +static void init_smp(struct cntrl_device *device) +{ + struct _host_type *hosts; + int i; + if (!device) + return; + + for (hosts = device->hosts; hosts; hosts = hosts->next) { + /* already initialized */ + if (hosts->ibpi_state_buffer) + continue; + hosts->ibpi_state_buffer = + calloc(hosts->ports, + sizeof(struct + gpio_tx_register_byte)); + + if (!hosts->ibpi_state_buffer) + continue; + + for (i = 0; i < hosts->ports; i++) + set_raw_pattern(i, &hosts->bitstream[0], + &ibpi2sgpio + [IBPI_PATTERN_ONESHOT_NORMAL].pattern); + hosts->flush = 0; + } +} + +/** + */ +int cntrl_init_smp(const char *path, struct cntrl_device *cntrl) +{ + char *path2 = NULL; + char *c; + int host, port = 0; + struct dirent *de; + DIR *d; + + if (!cntrl) + return port; + + /* Other case - just init controller. */ + if (path && strstr(path, "port-")) { + path2 = str_dup(path); + if (!path2) + return port; + + c = strstr(path2, "port-"); + if (!c) { + /* Should not happen. */ + log_debug("%s() missing 'port' in path '%s'", __func__, + path2); + free(path2); + return port; + } + c = strchr(c, '/'); + if (!c) { + free(path2); + return port; + } + *c = 0; + /* And now path2 has only up to 'port-...' string. */ + + /* this should open port-XX:X directory + * FIXME: for enclosure it may be port-XX:Y:Z but it's a second + * occurrence + * + * We may try this on the missing device. + * */ + d = opendir(path2); + if (!d) { + log_debug("%s() Error dir open '%s', path ='%s'", + __func__, path2, path); + free(path2); + return port; + } + while ((de = readdir(d))) { + if ((strcmp(de->d_name, ".") == 0) || + (strcmp(de->d_name, "..")) == 0) { + continue; + } + if (strncmp(de->d_name, "phy-", strlen("phy-")) == 0) { + /* Need link called "phy-XX:Y + * Y is real phy we need. + * This can also be found + * in phy_identifier file */ + if (sscanf(de->d_name, "phy-%d:%d", &host, &port) != 2) + continue; + + break; + } + } + closedir(d); + free(path2); + } + init_smp(cntrl); + return port; +} diff --git a/src/smp.h b/src/smp.h new file mode 100644 index 0000000..bac0386 --- /dev/null +++ b/src/smp.h @@ -0,0 +1,139 @@ +/* + * Intel(R) Enclosure LED Utilities + * Copyright (C) 2011-2017 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#include "ibpi.h" +#include "block.h" + +#ifndef _SMP_H_INCLUDED +#define _SMP_H_INCLUDED + +/* smp constants */ +#define SMP_FRAME_TYPE_REQ 0x40 +#define SMP_FRAME_TYPE_RESP 0x41 + +#define SMP_FUNC_GPIO_READ 0x02 +#define SMP_FUNC_GPIO_WRITE 0x82 + +#define SMP_FRAME_CRC_LEN sizeof(uint32_t) +#define SMP_DATA_CHUNK_SIZE sizeof(uint32_t) + +/* gpio constants */ +/* gpio register types */ +#define GPIO_REG_TYPE_CFG 0x00 +#define GPIO_REG_TYPE_RX 0x01 +#define GPIO_REG_TYPE_RX_GP 0x02 +#define GPIO_REG_TYPE_TX 0x03 +#define GPIO_REG_TYPE_TX_GP 0x04 + +/* gpio register indexes */ +#define GPIO_REG_IND_CFG_0 0x00 +#define GPIO_REG_IND_CFG_1 0x01 + +#define GPIO_REG_IND_RX_0 0x00 +#define GPIO_REG_IND_RX_1 0x01 + +#define GPIO_REG_IND_TX_0 0x00 +#define GPIO_REG_IND_TX_1 0x01 + +#define SG_RESPONSE_TIMEOUT (5 * 1000) /* 1000 as miliseconds multiplier */ +#define SCSI_MAX_CDB_LENGTH 0x10 + +#define GPIO_STATUS_OK 0x00 +#define GPIO_STATUS_FAILURE 0x80 + +struct gpio_tx_register_byte { + unsigned char error:3; + unsigned char locate:2; + unsigned char activity:3; +} __attribute__ ((__packed__)); + +/** + * @brief Sends message to SES processor of an enclosure. + * + * This function send a message to an enclosure in order to control LEDs of + * the given slot/component. It uses interface of ENCLOSURE kernel module to + * control LEDs. + * + * @param[in] device Path to an enclosure device in sysfs. + * @param[in] ibpi IBPI pattern to visualize. + * + * @return Number of characters written if successful or -1 in case of error + * and errno is set to appropriate error code. + */ +int scsi_ses_write(struct block_device *device, enum ibpi_pattern ibpi); + +/** + * @brief Write message to outbound raw byte stream buffer. + * + * @param[in] device Path to a smp device in sysfs. + * @param[in] ibpi IBPI pattern to visualize. + * + * @return 1 if successful or -1 in case of error + * and errno is set to appropriate error code. + */ +int scsi_smp_fill_buffer(struct block_device *device, enum ibpi_pattern ibpi); + +/** + * @brief Sends message to SMP device. + * + * This function triggers gpio order to control LEDs of + * the given component. + * + * @param[in] device Path to a smp device in sysfs. + * + * @return Number of bytes written to device if successful or -1 in case of error + * and errno is set to appropriate error code. + */ +int scsi_smp_write_buffer(struct block_device *device); + +/** + * @brief Init smp and gets phy index, + * + * @param[in] path Path to the device in sysfs. It can be NULL + * to just initialize cntrl and not to get the + * phy. + * @param[in] cntrl Controller device to be initialized. + * + * @return Phy index on success if path and cntrl weren't NULL + * 0 if error occurred or path was NULL. + */ +int cntrl_init_smp(const char *path, struct cntrl_device *cntrl); + +/** + * @brief Write GPIO data + * + * @param[in] path Path to the device in sysfs. + * phy. + * + * @param[in] smp_reg_type GPIO register type + * + * @param[in] smp_reg_index GPIO register index + * + * @param[in] smp_reg_count GPIO register count + * + * @param[in] data Data to be written + * + * @return written register count + * <0 if error occurred + */ +int smp_write_gpio(const char *path, int smp_reg_type, + int smp_reg_index, int smp_reg_count, void *data, + size_t len); + +#endif /* _SCSI_H_INCLUDED_ */ diff --git a/src/status.c b/src/status.c new file mode 100644 index 0000000..c19f8d4 --- /dev/null +++ b/src/status.c @@ -0,0 +1,77 @@ +/* + * Intel(R) Enclosure LED Utilities + * Copyright (C) 2009-2019 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#include "config.h" +#include "status.h" + +/** + */ +#define _S_CODE(__code) \ + case __code: return #__code + +/** + */ +char *strstatus(status_t scode) +{ + switch (scode) { + _S_CODE(STATUS_SUCCESS); + _S_CODE(STATUS_BUFFER_OVERFLOW); + _S_CODE(STATUS_INVALID_NODE); + _S_CODE(STATUS_OUT_OF_MEMORY); + _S_CODE(STATUS_OUT_OF_RANGE); + _S_CODE(STATUS_DATA_ERROR); + _S_CODE(STATUS_IBPI_DETERMINE_ERROR); + _S_CODE(STATUS_INVALID_PATH); + _S_CODE(STATUS_INVALID_SUBOPTION); + _S_CODE(STATUS_INVALID_STATE); + _S_CODE(STATUS_NULL_POINTER); + _S_CODE(STATUS_SIZE_ERROR); + _S_CODE(STATUS_FILE_OPEN_ERROR); + _S_CODE(STATUS_FILE_READ_ERROR); + _S_CODE(STATUS_FILE_WRITE_ERROR); + _S_CODE(STATUS_FILE_LOCK_ERROR); + _S_CODE(STATUS_SYSFS_PATH_ERROR); + _S_CODE(STATUS_SYSFS_INIT_ERROR); + _S_CODE(STATUS_SYSFS_SCAN_ERROR); + _S_CODE(STATUS_SYSFS_RESET_ERROR); + _S_CODE(STATUS_DIR_OPEN_ERROR); + _S_CODE(STATUS_LIST_EMPTY); + _S_CODE(STATUS_LIST_INIT_ERROR); + _S_CODE(STATUS_BLOCK_LIST_ERROR); + _S_CODE(STATUS_VOLUM_LIST_ERROR); + _S_CODE(STATUS_CNTRL_LIST_ERROR); + _S_CODE(STATUS_SLAVE_LIST_ERROR); + _S_CODE(STATUS_CNTNR_LIST_ERROR); + _S_CODE(STATUS_INVALID_FORMAT); + _S_CODE(STATUS_LEDMON_INIT); + _S_CODE(STATUS_LEDMON_RUNNING); + _S_CODE(STATUS_ONEXIT_ERROR); + _S_CODE(STATUS_INVALID_CONTROLLER); + _S_CODE(STATUS_NOT_SUPPORTED); + _S_CODE(STATUS_STAT_ERROR); + _S_CODE(STATUS_CMDLINE_ERROR); + _S_CODE(STATUS_NOT_A_PRIVILEGED_USER); + _S_CODE(STATUS_ENCLO_LIST_ERROR); + _S_CODE(STATUS_SLOTS_LIST_ERROR); + _S_CODE(STATUS_CONFIG_FILE_ERROR); + _S_CODE(STATUS_LOG_FILE_ERROR); + default: + return "???"; + } +} diff --git a/src/status.h b/src/status.h new file mode 100644 index 0000000..aebcfd0 --- /dev/null +++ b/src/status.h @@ -0,0 +1,77 @@ +/* + * Intel(R) Enclosure LED Utilities + * Copyright (C) 2009-2019 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#ifndef _STATUS_H_INCLUDED_ +#define _STATUS_H_INCLUDED_ + +/** + */ +typedef int status_t; + +/** + */ +enum status_code { + STATUS_SUCCESS = 0, + STATUS_BUFFER_OVERFLOW, + STATUS_NULL_POINTER, + STATUS_OUT_OF_MEMORY, + STATUS_OUT_OF_RANGE, + STATUS_INVALID_NODE, + STATUS_DATA_ERROR, + STATUS_IBPI_DETERMINE_ERROR, + STATUS_INVALID_PATH, + STATUS_INVALID_SUBOPTION, + STATUS_INVALID_STATE, + STATUS_SIZE_ERROR, + STATUS_FILE_OPEN_ERROR, + STATUS_FILE_READ_ERROR, + STATUS_FILE_WRITE_ERROR, + STATUS_FILE_LOCK_ERROR, + STATUS_DIR_OPEN_ERROR, + STATUS_SYSFS_PATH_ERROR, + STATUS_SYSFS_INIT_ERROR, + STATUS_SYSFS_SCAN_ERROR, + STATUS_SYSFS_RESET_ERROR, + STATUS_LIST_EMPTY, + STATUS_LIST_INIT_ERROR, + STATUS_BLOCK_LIST_ERROR, + STATUS_VOLUM_LIST_ERROR, + STATUS_CNTRL_LIST_ERROR, + STATUS_SLAVE_LIST_ERROR, + STATUS_CNTNR_LIST_ERROR, + STATUS_INVALID_FORMAT, + STATUS_LEDMON_INIT, + STATUS_LEDMON_RUNNING, + STATUS_ONEXIT_ERROR, + STATUS_INVALID_CONTROLLER, + STATUS_NOT_SUPPORTED, + STATUS_STAT_ERROR, + STATUS_CMDLINE_ERROR, + STATUS_NOT_A_PRIVILEGED_USER, + STATUS_ENCLO_LIST_ERROR, + STATUS_SLOTS_LIST_ERROR, + STATUS_CONFIG_FILE_ERROR, + STATUS_LOG_FILE_ERROR, +}; + +/** + */ +char *strstatus(status_t scode); + +#endif /* _STATUS_H_INCLUDED_ */ diff --git a/src/sysfs.c b/src/sysfs.c new file mode 100644 index 0000000..d4622dc --- /dev/null +++ b/src/sysfs.c @@ -0,0 +1,672 @@ +/* + * Intel(R) Enclosure LED Utilities + * Copyright (C) 2009-2019 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + + +#include +#include +#include +#include +#include +#include +#include + +#if _HAVE_DMALLOC_H +#include +#endif + +#include "block.h" +#include "cntrl.h" +#include "config.h" +#include "config_file.h" +#include "enclosure.h" +#include "ibpi.h" +#include "list.h" +#include "pci_slot.h" +#include "raid.h" +#include "slave.h" +#include "stdio.h" +#include "sysfs.h" +#include "utils.h" + +/** + */ +#define SYSFS_CLASS_BLOCK "/sys/block" +#define SYSFS_CLASS_ENCLOSURE "/sys/class/enclosure" +#define SYSFS_PCI_DEVICES "/sys/bus/pci/devices" +#define SYSFS_PCI_SLOTS "/sys/bus/pci/slots" + +/** + * This is internal variable global to sysfs module only. It is a list of + * block devices registered in the system. Use sysfs_init() + * function to initialize the variable. Use sysfs_scan() function to populate + * the list. Use sysfs_reset() function to delete the content of the list. + */ +static struct list sysfs_block_list; + +/** + * This is internal variable global to sysfs module only. It is a list of + * RAID volumes registered in the system. Use sysfs_init() + * function to initialize the variable. Use sysfs_scan() function to populate + * the list. Use sysfs_reset() function to delete the content of the list. + */ +static struct list volum_list; + +/** + * This is internal variable global to sysfs module only. It is a list of + * storage controller devices registered in the system and + * supported by Intel(R) Enclosure LEDs Control Utility. Use sysfs_init() + * function to initialize the variable. Use sysfs_scan() function to populate + * the list. Use sysfs_reset() function to delete the content of the list. + */ +static struct list cntrl_list; + +/** + * This is internal variable global to sysfs module only. It is a list of + * slave devices registered in the system. Use sysfs_init() + * function to initialize the variable. Use sysfs_scan() function to populate + * the list. Use sysfs_reset() function to delete the content of the list. + */ +static struct list slave_list; + +/** + * This is internal variable global to sysfs module only. It is a list of + * RAID containers registered in the system. Use sysfs_init() + * function to initialize the variable. Use sysfs_scan() function to populate + * the list. Use sysfs_reset() function to delete the content of the list. + */ +static struct list cntnr_list; + +/** + * This is internal variable global to sysfs module only. It is a to list of + * enclosures registered in the system. + */ +static struct list enclo_list; + +/** + * This is internal variable global to sysfs module only. It is a list of + * PCI slots registered in the system. Use sysfs_init() + * function to initialize the variable. Use sysfs_scan() function to populate + * the list. Use sysfs_reset() function to delete the content of the list. + */ +static struct list slots_list; + +/** + * @brief Determine device type. + * + * This is internal function of sysfs module. The function determines a type of + * RAID device either it is VOLUME or CONTAINER device. The information required + * if read from 'metadata_version' attribute from sysfs. External and native + * RAID devices are reported as volumes with no distinction between both types. + * + * @param[in] path Path to RAID device in sysfs tree. + * + * @return Type of RAID device if successful, otherwise DEVICE_TYPE_UNKNOWN. + */ +static enum device_type _get_device_type(const char *path) +{ + enum device_type result = DEVICE_TYPE_UNKNOWN; + char *p = get_text(path, "md/metadata_version"); + if (p != NULL) { + if (strlen(p) > 0) { + if (strncmp(p, "external:", 9) == 0) { + if (p[9] == '/' || p[9] == '-') + result = DEVICE_TYPE_VOLUME; + else + result = DEVICE_TYPE_CONTAINER; + } else { + result = DEVICE_TYPE_VOLUME; + } + } + free(p); + } + return result; +} + +/** + * @brief Gets device major and minor. + * + * This is internal function of sysfs module. The function retrieves major and + * minor of device from sysfs attribute. Each block device has 'dev' attribute + * where major and minor separated by colon are stored. + * + * @param[in] path Path to block device in sysfs tree. + * @param[in] d_id Placeholder where major and minor of device + * will be stored. If this argument is NULL the + * behavior of function is unspecified. + * + * @return The function does not return a value. + */ +static void _get_id(const char *path, struct device_id *d_id) +{ + char temp[PATH_MAX]; + + snprintf(temp, sizeof(temp), "%s/dev", path); + get_id(temp, d_id); +} + +/** + * @brief Adds slave device to RAID volume. + * + * This is internal function of sysfs module. The function puts slave device on + * list of slave devices of RAID volume. The memory is allocated and structure + * fields populated. RAID device is link to slave device. + * + * @param[in] path Path to 'md' directory of RAID device in sysfs + * tree. + * @param[in] raid Pointer to RAID device structure corresponding + * to 'path' argument. + * + * @return The function does not return a value. + */ +static void _slave_vol_add(const char *path, struct raid_device *raid) +{ + struct slave_device *device; + + char *t = strrchr(path, '/'); + if (strncmp(t + 1, "dev-", 4) == 0) { + device = slave_device_init(path, &sysfs_block_list); + if (device) { + device->raid = raid; + list_append(&slave_list, device); + } + } +} + +/** + * @brief Checks for duplicate entries on list of slave devices. + * + * This is internal function of sysfs module. The functions checks if the given + * slave device is already on list with slave devices. This function is used by + * _slave_cnt_add() function to avoid duplicate entries. + * + * @param[in] slave Pointer to slave device structure to check. + * + * @return 1 the given device is on the list, otherwise the function returns 0. + */ +static int _is_duplicate(struct slave_device *slave) +{ + struct slave_device *device; + + list_for_each(&slave_list, device) { + if (device->block == slave->block) + return 1; + } + return 0; +} + +/** + * @brief Checks if given disk can be removed from sysfs_block_list if + * metatada is not present. + * + * This is internal function (action) of sysfs module. The slave_list keeps + * all devices with metadata (raid devices). If disk is not included in slave + * list there is not metadata on it. + * + * @return 1 if can be removed, otherwise 0. + */ +static int _is_non_raid_device(struct block_device *block_device) +{ + struct slave_device *slave_device; + + list_for_each(&slave_list, slave_device) { + if (strcmp(slave_device->block->sysfs_path, + block_device->sysfs_path) == 0) + return 0; + } + + return 1; +} + +/** + */ +static void _slave_cnt_add(const char *path, struct raid_device *raid) +{ + struct slave_device *device; + + char *t = strrchr(path, '/'); + if (strncmp(t + 1, "dev-", 4) == 0) { + device = slave_device_init(path, &sysfs_block_list); + if (device) { + if (!_is_duplicate(device)) { + device->raid = raid; + list_append(&slave_list, device); + } else { + slave_device_fini(device); + } + } + } +} + +static void _link_raid_device(struct raid_device *device, enum device_type type) +{ + char temp[PATH_MAX]; + struct list dir; + + snprintf(temp, sizeof(temp), "%s/md", device->sysfs_path); + + if (scan_dir(temp, &dir) == 0) { + const char *dir_path; + + list_for_each(&dir, dir_path) { + if (type == DEVICE_TYPE_VOLUME) + _slave_vol_add(dir_path, device); + else if (type == DEVICE_TYPE_CONTAINER) + _slave_cnt_add(dir_path, device); + } + list_erase(&dir); + } +} + +/** + */ +static void _block_add(const char *path) +{ + struct block_device *device = block_device_init(&cntrl_list, path); + if (device) + list_append(&sysfs_block_list, device); +} + +/** + */ +static void _volum_add(const char *path, unsigned int device_num) +{ + struct raid_device *device = + raid_device_init(path, device_num, DEVICE_TYPE_VOLUME); + if (device) + list_append(&volum_list, device); +} + +/** + */ +static void _cntnr_add(const char *path, unsigned int device_num) +{ + struct raid_device *device = + raid_device_init(path, device_num, DEVICE_TYPE_CONTAINER); + if (device) + list_append(&cntnr_list, device); +} + +/** + */ +static void _raid_add(const char *path) +{ + struct device_id device_id; + + _get_id(path, &device_id); + if (device_id.major == 9) { + switch (_get_device_type(path)) { + case DEVICE_TYPE_VOLUME: + _volum_add(path, device_id.minor); + break; + case DEVICE_TYPE_CONTAINER: + _cntnr_add(path, device_id.minor); + break; + case DEVICE_TYPE_UNKNOWN: + break; + } + } +} + +/** + */ +static void _cntrl_add(const char *path) +{ + struct cntrl_device *device = cntrl_device_init(path); + if (device) + list_append(&cntrl_list, device); +} + +/** + */ +static void _enclo_add(const char *path) +{ + struct enclosure_device *device = enclosure_device_init(path); + if (device) + list_append(&enclo_list, device); +} + +/** + */ +static void _slots_add(const char *path) +{ + struct pci_slot *device = pci_slot_init(path); + if (device) + list_append(&slots_list, device); +} + +/** + */ +static void _check_raid(const char *path) +{ + char *t = strrchr(path, '/'); + if (strncmp(t + 1, "md", 2) == 0) + _raid_add(path); +} + +/** + */ +static void _check_cntrl(const char *path) +{ + char link[PATH_MAX]; + if (realpath(path, link) != NULL) + _cntrl_add(link); +} + +/** + */ +static void _check_enclo(const char *path) +{ + char link[PATH_MAX]; + if (realpath(path, link) != NULL) + _enclo_add(link); +} + +static void _scan_block(void) +{ + struct list dir; + if (scan_dir(SYSFS_CLASS_BLOCK, &dir) == 0) { + const char *dir_path; + + list_for_each(&dir, dir_path) + _block_add(dir_path); + list_erase(&dir); + } +} + +static void _scan_raid(void) +{ + struct list dir; + if (scan_dir(SYSFS_CLASS_BLOCK, &dir) == 0) { + const char *dir_path; + + list_for_each(&dir, dir_path) + _check_raid(dir_path); + list_erase(&dir); + } +} + +static void _scan_cntrl(void) +{ + struct list dir; + if (scan_dir(SYSFS_PCI_DEVICES, &dir) == 0) { + const char *dir_path; + + list_for_each(&dir, dir_path) + _check_cntrl(dir_path); + list_erase(&dir); + } +} + +static void _scan_slave(void) +{ + struct raid_device *device; + + list_for_each(&volum_list, device) + _link_raid_device(device, DEVICE_TYPE_VOLUME); + list_for_each(&cntnr_list, device) + _link_raid_device(device, DEVICE_TYPE_CONTAINER); + if (conf.raid_members_only) { + struct node *node; + + list_for_each_node(&sysfs_block_list, node) { + if (_is_non_raid_device(node->item)) + list_delete(node); + } + } +} + +static void _scan_enclo(void) +{ + struct list dir; + if (scan_dir(SYSFS_CLASS_ENCLOSURE, &dir) == 0) { + const char *dir_path; + + list_for_each(&dir, dir_path) + _check_enclo(dir_path); + list_erase(&dir); + } +} + +static void _scan_slots(void) +{ + struct list dir; + if (scan_dir(SYSFS_PCI_SLOTS, &dir) == 0) { + const char *dir_path; + + list_for_each(&dir, dir_path) + _slots_add(dir_path); + list_erase(&dir); + } +} + +/** + */ +static int _is_failed_array(struct raid_device *raid) +{ + if (raid->degraded > 0) { + switch (raid->level) { + case RAID_LEVEL_1: + case RAID_LEVEL_10: + return (raid->degraded == raid->raid_disks); + case RAID_LEVEL_4: + case RAID_LEVEL_5: + return (raid->degraded > 1); + case RAID_LEVEL_6: + return (raid->degraded > 2); + case RAID_LEVEL_LINEAR: + case RAID_LEVEL_UNKNOWN: + case RAID_LEVEL_0: + break; + case RAID_LEVEL_FAULTY: + return 1; + } + } + return -1; +} + +/** + */ +static void _set_block_state(struct block_device *block, enum ibpi_pattern ibpi) +{ + char *debug_dev = strrchr(block->sysfs_path, '/'); + debug_dev = debug_dev ? debug_dev + 1 : block->sysfs_path; + log_debug("(%s): device: %s, state: %s", __func__, debug_dev, + ibpi2str(ibpi)); + if (block->ibpi < ibpi) + block->ibpi = ibpi; +} + +/** + */ +static void _set_array_state(struct raid_device *raid, + struct block_device *block) +{ + switch (raid->sync_action) { + case RAID_ACTION_UNKNOWN: + case RAID_ACTION_IDLE: + case RAID_ACTION_FROZEN: + _set_block_state(block, IBPI_PATTERN_NORMAL); + break; + case RAID_ACTION_RESHAPE: + if (conf.blink_on_migration) + _set_block_state(block, IBPI_PATTERN_REBUILD); + break; + case RAID_ACTION_CHECK: + case RAID_ACTION_RESYNC: + case RAID_ACTION_REPAIR: + if (conf.blink_on_init) + _set_block_state(block, IBPI_PATTERN_REBUILD); + break; + case RAID_ACTION_RECOVER: + if (conf.rebuild_blink_on_all) + _set_block_state(block, IBPI_PATTERN_REBUILD); + break; + } +} + +/** + */ +static void _determine(struct slave_device *device) +{ + if (!device->block->raid_dev || + (device->block->raid_dev->type == DEVICE_TYPE_CONTAINER && + device->raid->type == DEVICE_TYPE_VOLUME)) { + raid_device_fini(device->block->raid_dev); + device->block->raid_dev = raid_device_duplicate(device->raid); + } + + if ((device->state & SLAVE_STATE_FAULTY) != 0) { + _set_block_state(device->block, IBPI_PATTERN_FAILED_DRIVE); + } else if ((device-> + state & (SLAVE_STATE_BLOCKED | SLAVE_STATE_WRITE_MOSTLY)) != 0) { + _set_block_state(device->block, IBPI_PATTERN_NORMAL); + } else if ((device->state & SLAVE_STATE_SPARE) != 0) { + if (_is_failed_array(device->raid) == 0) { + if (device->raid->sync_action != RAID_ACTION_RESHAPE || + conf.blink_on_migration == 1) + _set_block_state(device->block, + IBPI_PATTERN_REBUILD); + } else { + _set_block_state(device->block, IBPI_PATTERN_HOTSPARE); + } + } else if ((device->state & SLAVE_STATE_IN_SYNC) != 0) { + switch (_is_failed_array(device->raid)) { + case 0: + _set_block_state(device->block, IBPI_PATTERN_DEGRADED); + break; + case 1: + _set_block_state(device->block, + IBPI_PATTERN_FAILED_ARRAY); + break; + } + _set_array_state(device->raid, device->block); + } +} + +static void _determine_slaves(struct list *local_slave_list) +{ + struct slave_device *device; + + list_for_each(local_slave_list, device) + _determine(device); +} + +void sysfs_init(void) +{ + list_init(&sysfs_block_list, (item_free_t)block_device_fini); + list_init(&volum_list, (item_free_t)raid_device_fini); + list_init(&cntrl_list, (item_free_t)cntrl_device_fini); + list_init(&slave_list, (item_free_t)slave_device_fini); + list_init(&cntnr_list, (item_free_t)raid_device_fini); + list_init(&enclo_list, (item_free_t)enclosure_device_fini); + list_init(&slots_list, (item_free_t)pci_slot_fini); +} + +void sysfs_reset(void) +{ + list_erase(&sysfs_block_list); + list_erase(&volum_list); + list_erase(&cntrl_list); + list_erase(&slave_list); + list_erase(&cntnr_list); + list_erase(&enclo_list); + list_erase(&slots_list); +} + +void sysfs_scan(void) +{ + _scan_enclo(); + _scan_cntrl(); + _scan_slots(); + _scan_block(); + _scan_raid(); + _scan_slave(); + + _determine_slaves(&slave_list); +} + +/* + * The function reutrns list of enclosure devices attached to SAS/SCSI storage + * controller(s). + */ +const struct list *sysfs_get_enclosure_devices(void) +{ + return &enclo_list; +} + +/* + * The function returns list of controller devices present in the system. + */ +const struct list *sysfs_get_cntrl_devices(void) +{ + return &cntrl_list; +} + +/* + * The function returns list of RAID volumes present in the system. + */ +const struct list *sysfs_get_volumes(void) +{ + return &volum_list; +} + +const struct list *sysfs_get_block_devices(void) +{ + return &sysfs_block_list; +} + +const struct list *sysfs_get_pci_slots(void) +{ + return &slots_list; +} + +/* + * The function checks if the given storage controller has enclosure device(s) + * attached. + */ +int sysfs_enclosure_attached_to_cntrl(const char *path) +{ + struct enclosure_device *device; + + list_for_each(&enclo_list, device) { + if ((device->sysfs_path != NULL) && + (strncmp(device->sysfs_path, path, strlen(path)) == 0)) + return 1; + } + return 0; +} + +/* + * This function checks driver type. + */ +int sysfs_check_driver(const char *path, const char *driver) +{ + char buf[PATH_MAX]; + char driver_path[PATH_MAX]; + char *link; + int found = 0; + + snprintf(buf, sizeof(buf), "%s/driver", path); + snprintf(driver_path, sizeof(driver_path), "/%s", driver); + link = realpath(buf, NULL); + if (link && strstr(link, driver_path)) + found = 1; + free(link); + return found; +} diff --git a/src/sysfs.h b/src/sysfs.h new file mode 100644 index 0000000..efebea8 --- /dev/null +++ b/src/sysfs.h @@ -0,0 +1,86 @@ +/* + * Intel(R) Enclosure LED Utilities + * Copyright (C) 2009-2018 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#ifndef _SYSFS_H_INCLUDED_ +#define _SYSFS_H_INCLUDED_ + +#include "status.h" + +/** + * @brief Initializes sysfs module. + * + * This function initializes sysfs module internal lists. + * Application must call this function before any sysfs module function. + */ +void sysfs_init(void); + +/** + * @brief Resets the content of internal lists. + * + * This function releases memory allocated for elements of internal lists. + */ +void sysfs_reset(void); + +/** + * @brief Scans sysfs tree and populates internal lists. + * + * This function scans sysfs tree for storage controllers, block devices, RAID + * devices, container devices, slave devices and enclosure devices registered + * in the system. Only supported block and controller devices are put on a list. + */ +void sysfs_scan(void); + +/** + * The function returns list of enclosure devices attached to SAS/SCSI storage + * controller(s). + */ +const struct list *sysfs_get_enclosure_devices(void); + +/** + * The function returns list of controller devices present in the system. + */ +const struct list *sysfs_get_cntrl_devices(void); + +/** + * The function returns list of RAID volumes present in the system. + */ +const struct list *sysfs_get_volumes(void); + +/** + * The function returns list of block devices present in the system. + */ +const struct list *sysfs_get_block_devices(void); + +/** + * The function returns list of pci slots present in the system. + */ +const struct list *sysfs_get_pci_slots(void); + +/** + * The function checks if the given storage controller is attached to enclosure + * device(s). + */ +int sysfs_enclosure_attached_to_cntrl(const char *path); + +/* + * This function checks driver type. + */ +int sysfs_check_driver(const char *path, const char *driver); + +#endif /* _SYSFS_H_INCLUDED_ */ diff --git a/src/udev.c b/src/udev.c new file mode 100644 index 0000000..9cbc467 --- /dev/null +++ b/src/udev.c @@ -0,0 +1,213 @@ +/* + * Intel(R) Enclosure LED Utilities + * Copyright (C) 2017-2020 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#include +#include +#include +#include + +#include "block.h" +#include "ibpi.h" +#include "status.h" +#include "sysfs.h" +#include "udev.h" +#include "utils.h" + +static struct udev_monitor *udev_monitor; + +static int _compare(const struct block_device *bd, const char *syspath) +{ + if (!bd || !syspath) + return 0; + + if (strcmp(bd->sysfs_path, syspath) == 0) { + return 1; + } else { + struct block_device *bd_new; + int ret; + + bd_new = block_device_init(sysfs_get_cntrl_devices(), syspath); + if (!bd_new) + return 0; + + ret = block_compare(bd, bd_new); + block_device_fini(bd_new); + + return ret; + } +} + +static int create_udev_monitor(void) +{ + int res; + struct udev *udev = udev_new(); + + if (!udev) { + log_error("Failed to create udev context instance."); + return -1; + } + + udev_monitor = udev_monitor_new_from_netlink(udev, "udev"); + if (!udev_monitor) { + log_error("Failed to create udev monitor object."); + udev_unref(udev); + return -1; + } + + res = udev_monitor_filter_add_match_subsystem_devtype(udev_monitor, + "block", "disk"); + if (res < 0) { + log_error("Failed to modify udev monitor filters."); + stop_udev_monitor(); + return -1; + } + + res = udev_monitor_enable_receiving(udev_monitor); + if (res < 0) { + log_error("Failed to switch udev monitor to listening mode."); + stop_udev_monitor(); + return -1; + } + + return udev_monitor_get_fd(udev_monitor); +} + +void stop_udev_monitor(void) +{ + if (udev_monitor) { + struct udev *udev = udev_monitor_get_udev(udev_monitor); + + udev_monitor_unref(udev_monitor); + + if (udev) + udev_unref(udev); + } +} + +int get_udev_monitor(void) +{ + if (udev_monitor) + return udev_monitor_get_fd(udev_monitor); + + return create_udev_monitor(); +} + +static int _check_raid(const char *path) +{ + char *t = strrchr(path, '/'); + + if (t == NULL) + return 0; + return strncmp(t + 1, "md", 2) == 0; +} + +static enum udev_action _get_udev_action(const char *action) +{ + enum udev_action ret = UDEV_ACTION_UNKNOWN; + + if (strncmp(action, "add", 3) == 0) + ret = UDEV_ACTION_ADD; + else if (strncmp(action, "remove", 6) == 0) + ret = UDEV_ACTION_REMOVE; + return ret; +} + +static void _clear_raid_dev_info(struct block_device *block, char *raid_dev) +{ + if (block->raid_dev && block->raid_dev->sysfs_path) { + char *tmp = strrchr(block->raid_dev->sysfs_path, '/'); + + if (tmp == NULL) { + log_debug("Device: %s have wrong raid_dev path: %s", + block->sysfs_path, + block->raid_dev->sysfs_path); + return; + } + if (strcmp(raid_dev, tmp + 1) == 0) { + log_debug("CLEAR raid_dev %s in %s ", + raid_dev, block->sysfs_path); + raid_device_fini(block->raid_dev); + block->raid_dev = NULL; + } + } + +} + +int handle_udev_event(struct list *ledmon_block_list) +{ + struct udev_device *dev; + int status = -1; + + dev = udev_monitor_receive_device(udev_monitor); + if (dev) { + const char *action = udev_device_get_action(dev); + enum udev_action act = _get_udev_action(action); + const char *syspath = udev_device_get_syspath(dev); + struct block_device *block = NULL; + + if (act == UDEV_ACTION_UNKNOWN) { + status = 1; + goto exit; + } + + list_for_each(ledmon_block_list, block) { + if (_compare(block, syspath)) + break; + block = NULL; + } + + if (!block) { + if (act == UDEV_ACTION_REMOVE && _check_raid(syspath)) { + /*ledmon is interested about removed arrays*/ + char *dev_name; + + dev_name = strrchr(syspath, '/') + 1; + log_debug("REMOVED %s", dev_name); + list_for_each(ledmon_block_list, block) + _clear_raid_dev_info(block, dev_name); + status = 0; + goto exit; + } + status = 1; + goto exit; + } + + if (act == UDEV_ACTION_ADD) { + log_debug("ADDED %s", block->sysfs_path); + if (block->ibpi == IBPI_PATTERN_FAILED_DRIVE || + block->ibpi == IBPI_PATTERN_REMOVED || + block->ibpi == IBPI_PATTERN_UNKNOWN) + block->ibpi = IBPI_PATTERN_ADDED; + } else if (act == UDEV_ACTION_REMOVE) { + log_debug("REMOVED %s", block->sysfs_path); + block->ibpi = IBPI_PATTERN_REMOVED; + } else { + /* not interesting event */ + status = 1; + goto exit; + } + status = 0; + } else { + return -1; + } + +exit: + udev_device_unref(dev); + return status; +} diff --git a/src/udev.h b/src/udev.h new file mode 100644 index 0000000..ae899a0 --- /dev/null +++ b/src/udev.h @@ -0,0 +1,65 @@ +/* + * Intel(R) Enclosure LED Utilities + * Copyright (C) 2017-2019 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#ifndef _UDEV_H_INCLUDED_ +#define _UDEV_H_INCLUDED_ + +#include "list.h" + +/** + */ +enum udev_action { + UDEV_ACTION_UNKNOWN = 0, + UDEV_ACTION_ADD, + UDEV_ACTION_REMOVE +}; + +/** + * @brief Deletes udev context and udev monitor. + * + * @return The function does not return a value.. + */ +void stop_udev_monitor(void); + +/** + * @brief Returns udev monitor file descriptor or creates udev context and + * udev monitor if monitor does not exist. + * + * @return Udev monitor file descriptor if successful, otherwise the + * function returns -1 on libudev error and libudev sets errno. + */ +int get_udev_monitor(void); + +/** + * @brief Handles udev event. + * + * This function checks event type and if it is 'add' or remove + * function sets custom IBPI pattern to block device which is affected + * by this event. + * + * @param[in] ledmon_block_list list containing block devices, it is + * used to match device from udev event. + * + * @return 0 if 'add' or 'remove' event handled successfully; + * 1 if registered event is not 'add' or 'remove'; + * -1 on libudev error. + */ +int handle_udev_event(struct list *ledmon_block_list); + +#endif /* _UDEV_H_INCLUDED_ */ diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 0000000..dfe3637 --- /dev/null +++ b/src/utils.c @@ -0,0 +1,643 @@ +/* + * Intel(R) Enclosure LED Utilities + * Copyright (C) 2009-2019 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if _HAVE_DMALLOC_H +#include +#endif + +#include "config.h" +#include "list.h" +#include "status.h" +#include "utils.h" + +/** + */ +#define TIMESTAMP_PATTERN "%b %d %T " + +/** + * Name of the executable. It is the last section of invocation path. + */ +char *progname = NULL; + +/** + */ +static FILE *s_log = NULL; + +struct log_level_info log_level_infos[] = { + [LOG_LEVEL_DEBUG] = {PREFIX_DEBUG, LOG_DEBUG}, + [LOG_LEVEL_WARNING] = {PREFIX_WARNING, LOG_WARNING}, + [LOG_LEVEL_INFO] = {PREFIX_INFO, LOG_INFO}, + [LOG_LEVEL_ERROR] = {PREFIX_ERROR, LOG_ERR} +}; + +/* + * Function returns a content of a text file. See utils.h for details. + */ +char *get_text(const char *path, const char *name) +{ + char temp[PATH_MAX]; + + snprintf(temp, sizeof(temp), "%s/%s", path, name); + return buf_read(temp); +} + +/* + * Function returns integer value (1 or 0) based on a boolean value ('Y' or 'N') + * read from a text file. See utils.h for details. + */ +int get_bool(const char *path, int defval, const char *name) +{ + char *p = get_text(path, name); + if (p) { + if (*p == 'Y') + defval = 1; + else if (*p == 'N') + defval = 0; + free(p); + } + return defval; +} + +/* + * Function returns 64-bit unsigned integer value read from a text file. See + * utils.h for details. + */ +uint64_t get_uint64(const char *path, uint64_t defval, const char *name) +{ + char *p = get_text(path, name); + uint64_t retval = defval; + + if (p) { + if (sscanf(p, "%" SCNx64, &defval) == 1) + retval = defval; + free(p); + } + return retval; +} + +/* + * Function returns integer value read from a text file. + * See utils.h for details. + */ +int get_int(const char *path, int defval, const char *name) +{ + char *p = get_text(path, name); + if (p) { + defval = atoi(p); + free(p); + } + return defval; +} + +/** + */ +int scan_dir(const char *path, struct list *result) +{ + struct dirent *dirent; + int ret = 0; + DIR *dir = opendir(path); + if (!dir) + return -1; + + list_init(result, NULL); + + while ((dirent = readdir(dir)) != NULL) { + char *str; + size_t len; + + if ((strcmp(dirent->d_name, ".") == 0) || + (strcmp(dirent->d_name, "..")) == 0) + continue; + + len = strlen(path) + strlen(dirent->d_name) + 2; + str = malloc(len); + if (!str) { + ret = -1; + list_erase(result); + break; + } + + snprintf(str, len, "%s/%s", path, dirent->d_name); + + list_append(result, str); + } + closedir(dir); + + return ret; +} + +/** + */ +static int _is_virtual(int dev_type) +{ + switch (dev_type) { + case 0: /* sysfs */ + case 3: /* procfs */ + return 1; + } + return 0; +} + +/** + */ +ssize_t buf_write(const char *path, const char *buf) +{ + int fd; + ssize_t size = -1; + + if (path == NULL) + __set_errno_and_return(EINVAL); + if ((buf == NULL) || (strlen(buf) == 0)) + __set_errno_and_return(ENODATA); + fd = open(path, O_WRONLY); + if (fd >= 0) { + size = write(fd, buf, strlen(buf)); + close(fd); + } + return size; +} + +/** + */ +char *buf_read(const char *path) +{ + struct stat st; + int fd, size; + char *buf, *t; + + if (stat(path, &st) < 0) + return NULL; + if (st.st_size == 0) { + if (!_is_virtual(st.st_dev)) + return NULL; + st.st_size = st.st_blksize; + } + if (_is_virtual(st.st_dev)) + st.st_size = st.st_blksize; + t = buf = malloc(st.st_size); + if (buf) { + fd = open(path, O_RDONLY); + if (fd >= 0) { + size = read(fd, buf, st.st_size); + close(fd); + if (size > 0) + t = strchrnul(buf, '\n'); + } + *t = '\0'; + } + return buf; +} + +/** + */ +void get_id(const char *path, struct device_id *did) +{ + char *t, *p; + + if (did && path) { + did->major = did->minor = -1; + p = buf_read(path); + if (p) { + t = strchr(p, ':'); + if (t) { + *(t++) = '\0'; + did->major = atoi(p); + did->minor = atoi(t); + } + free(p); + } + } +} + +/** + */ +static void _log_timestamp(void) +{ + time_t timestamp; + struct tm *t; + char buf[30]; + + timestamp = time(NULL); + t = localtime(×tamp); + + if (t) { + strftime(buf, sizeof(buf), TIMESTAMP_PATTERN, t); + fprintf(s_log, "%s", buf); + } +} + +/** + */ +int log_open(const char *path) +{ + if (s_log) + log_close(); + + s_log = fopen(path, "a"); + if (s_log == NULL) + return -1; + return 0; +} + +/** + */ +void log_close(void) +{ + if (s_log) { + fflush(s_log); + fclose(s_log); + s_log = NULL; + } +} + +/** + */ +void _log(enum log_level_enum loglevel, const char *buf, ...) +{ + va_list vl; + struct log_level_info *lli = &log_level_infos[loglevel]; + + if (s_log == NULL) + log_open(conf.log_path); + + if (conf.log_level >= loglevel) { + char msg[4096]; + va_start(vl, buf); + vsnprintf(msg, sizeof(msg), buf, vl); + va_end(vl); + if (s_log) { + _log_timestamp(); + fprintf(s_log, "%s", lli->prefix); + fprintf(s_log, "%s\n", msg); + fflush(s_log); + } + syslog(lli->priority, "%s", msg); + } +} + +/** + * @brief Sets program's short name. + * + * This is internal function of monitor service. It is used to extract the name + * of executable file from command line argument. + * + * @param[in] invocation_name - the pointer to command line argument + * with the invocation name. + * + * @return The function does not return a value. + */ +void set_invocation_name(char *invocation_name) +{ +#ifdef program_invocation_short_name + (void)invocation_name; + progname = program_invocation_short_name; +#else + char *t = strrchr(invocation_name, PATH_DELIM); + if (t) + progname = t + 1; + else + progname = invocation_name; +#endif /* program_invocation_short_name */ +} + +/** + */ +char *str_cpy(char *dest, const char *src, size_t size) +{ + strncpy(dest, src, size - 1); + dest[size - 1] = '\0'; + return dest; +} + +/** + */ +char *str_dup(const char *src) +{ + char *ret; + + if (!src) + return NULL; + ret = strdup(src); + if (!ret) { + log_error("Cannot duplicate string"); + exit(EXIT_FAILURE); + } + return ret; +} + +char *get_path_hostN(const char *path) +{ + char *c = NULL, *s = NULL, *p = str_dup(path); + if (!p) + return NULL; + c = strstr(p, "host"); + if (!c) + goto end; + s = strchr(c, '/'); + if (!s) + goto end; + *s = 0; + s = str_dup(c); + end: + free(p); + return s; +} + +char *get_path_component_rev(const char *path, int index) +{ + int i; + char *c = NULL, *p = str_dup(path); + char *result = NULL; + for (i = 0; i <= index; i++) { + if (c) + *c = '\0'; + c = strrchr(p, '/'); + } + if (c) + result = str_dup(c + 1); + free(p); + return result; +} + +char *truncate_path_component_rev(const char *path, int index) +{ + int i; + char *c = NULL, *p = str_dup(path); + if (!p) + return NULL; + + for (i = 0; i <= index; i++) { + if (c) + *c = '\0'; + c = strrchr(p, '/'); + } + c = str_dup(p); + free(p); + return c; +} + +int match_string(const char *string, const char *pattern) +{ + int status; + regex_t regex; + + if (!string || !pattern) + return 0; + + if (strcmp(string, pattern) == 0) + return 1; + + status = regcomp(®ex, pattern, REG_EXTENDED); + if (status != 0) { + log_debug("regecomp failed, ret=%d", status); + return 0; + } + + status = regexec(®ex, string, 0, NULL, 0); + if (status != 0) + return 0; + + return 1; +} + +int get_log_fd(void) +{ + if (s_log) + return fileno(s_log); + return -1; +} + +void print_opt(const char *long_opt, const char *short_opt, const char *desc) +{ + printf("%-20s%-10s%s\n", long_opt, short_opt, desc); +} + +/** + * @brief Sets the path to local log file. + * + * This function sets the path and + * file name of log file. The function checks if the specified path is valid. In + * case of incorrect path the function does nothing. + * + * @param[in] path new location and name of log file. + * + * @return STATUS_SUCCESS if successful, otherwise a valid status_t status code. + * The following status code are returned: + * + * STATUS_INVALID_PATH the given path is invalid. + * STATUS_FILE_OPEN_ERROR unable to open a log file i.e. because of + * insufficient privileges. + */ +status_t set_log_path(const char *path) +{ + char temp[PATH_MAX]; + char log_file[PATH_MAX]; + char *resolved, *logdir, *logfile, *cpath; + + /* + * Extract directory from path + */ + cpath = str_dup(path); + logdir = dirname(cpath); + + /* + * Check if directory exists + */ + resolved = realpath(logdir, temp); + if (resolved == NULL) { + printf("%s: %s\n", strerror(errno), logdir); + free(cpath); + return STATUS_INVALID_PATH; + } + + free(cpath); + cpath = str_dup(path); + logfile = basename(cpath); + + snprintf(log_file, sizeof(log_file), "%s/%s", + resolved, logfile); + free(cpath); + + if (conf.log_path) + free(conf.log_path); + conf.log_path = str_dup(log_file); + + return STATUS_SUCCESS; +} + +/** + * Internal array with option tokens. It is used to help parse command line + * long options. + */ +struct option longopt_all[] = { + [OPT_ALL] = {"all", no_argument, NULL, '\0'}, + [OPT_CONFIG] = {"config", required_argument, NULL, 'c'}, + [OPT_DEBUG] = {"debug", no_argument, NULL, '\0'}, + [OPT_ERROR] = {"error", no_argument, NULL, '\0'}, + [OPT_HELP] = {"help", no_argument, NULL, 'h'}, + [OPT_INFO] = {"info", no_argument, NULL, '\0'}, + [OPT_INTERVAL] = {"interval", required_argument, NULL, 't'}, + [OPT_LOG] = {"log", required_argument, NULL, 'l'}, + [OPT_QUIET] = {"quiet", no_argument, NULL, '\0'}, + [OPT_VERSION] = {"version", no_argument, NULL, 'v'}, + [OPT_WARNING] = {"warning", no_argument, NULL, '\0'}, + [OPT_LOG_LEVEL] = {"log-level", required_argument, NULL, '\0'}, + [OPT_LIST_CTRL] = {"list-controllers", no_argument, NULL, 'L'}, + [OPT_LISTED_ONLY] = {"listed-only", no_argument, NULL, 'x'}, + [OPT_FOREGROUND] = {"foreground", no_argument, NULL, '\0'}, + [OPT_NULL_ELEMENT] = {NULL, no_argument, NULL, '\0'} +}; + +void setup_options(struct option **_longopt, char **_shortopt, int *options, int + options_nr) +{ + struct option *longopt; + char *shortopt; + int i, j = 0; + struct option *opt; + + longopt = malloc(sizeof(struct option) * (options_nr + 1)); + shortopt = calloc(options_nr * 2 + 1, sizeof(char)); + if (!longopt || !shortopt) { + fprintf(stderr, "Out of memory\n"); + exit(STATUS_OUT_OF_MEMORY); + } + for (i = 0; i < options_nr; i++) { + opt = &longopt_all[options[i]]; + longopt[i] = *opt; + if (opt->val != '\0') { + shortopt[j++] = (char) opt->val; + if (opt->has_arg) + shortopt[j++] = ':'; + } + } + longopt[i] = longopt_all[OPT_NULL_ELEMENT]; + shortopt[j] = '\0'; + + *_longopt = longopt; + *_shortopt = shortopt; +} + +/** + * @brief Gets id for given CLI option which corresponds to value from longopt + * table. + * + * This is internal function of monitor service. The function maps given string + * to the value from longopt enum and returns id of matched element. Generic + * parameters allow to use this function for any CLI options-table which bases + * on option struct. + * + * @param[in] optarg String containing value given by user in CLI. + * + * @return integer id if successful, otherwise a -1. + */ +int get_option_id(const char *optarg) +{ + int i = 0; + + while (longopt_all[i].name != NULL) { + if (strcmp(longopt_all[i].name, optarg) == 0) + return i; + i++; + } + return -1; +} + + +/** + * @brief Sets verbose variable to given level. + * + * This is internal function of monitor service. The function maps given level + * to the value from verbose_level enum and sets verbose value to ledmon + * configuration. + * + * @param[in] log_level required new log_level. + * + * @return STATUS_SUCCESS if successful, otherwise a valid status_t status code. + */ +status_t set_verbose_level(int log_level) +{ + int new_verbose = -1; + + switch (log_level) { + case OPT_ALL: + new_verbose = LOG_LEVEL_ALL; + break; + case OPT_DEBUG: + new_verbose = LOG_LEVEL_DEBUG; + break; + case OPT_ERROR: + new_verbose = LOG_LEVEL_ERROR; + break; + case OPT_INFO: + new_verbose = LOG_LEVEL_INFO; + break; + case OPT_QUIET: + new_verbose = LOG_LEVEL_QUIET; + break; + case OPT_WARNING: + new_verbose = LOG_LEVEL_WARNING; + break; + } + if (new_verbose != -1) { + conf.log_level = new_verbose; + return STATUS_SUCCESS; + } + return STATUS_CMDLINE_ERROR; +} + +const char *ibpi2str(enum ibpi_pattern ibpi) +{ +#ifdef _TEST_CONFIG + return NULL; +#else + static char buf[20]; + const char *ret; + + if (ibpi >= 0 && ibpi < ibpi_pattern_count) + ret = ibpi_str[ibpi]; + else + ret = NULL; + + if (!ret) { + snprintf(buf, sizeof(buf), "(unknown: %u)", ibpi); + ret = buf; + } + + return ret; +#endif +} diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..cfd6855 --- /dev/null +++ b/src/utils.h @@ -0,0 +1,367 @@ +/* + * Intel(R) Enclosure LED Utilities + * Copyright (C) 2009-2019 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#ifndef _UTILS_H_INCLUDED_ +#define _UTILS_H_INCLUDED_ + +#include +#include "config_file.h" +#include "stdlib.h" +#include "stdint.h" +#include "list.h" +#include "status.h" +#include "syslog.h" +#include "ibpi.h" + +/** + * Value is intentionally unused. + * Macro to prevent compiler from raising unused result warning. + */ +#define UNUSED(x) do { if (x); } while (0) + +/** + * Maximum number of bytes in temporary buffer. It is used for local variables. + */ +#define BUFFER_MAX 128 + +/** + * Maximum number of bytes in write buffer. It is used for local variables when + * function needs to write a sysfs attribute. + */ +#define WRITE_BUFFER_SIZE 1024 + +/** + * This structure describes a device identifier. It consists of major and minor + * attributes of device. + */ +struct device_id { + int major, minor; +}; + +struct log_level_info { + char prefix[10]; + int priority; +}; + +/** + */ +#define PREFIX_DEBUG " DEBUG: " +#define PREFIX_WARNING "WARNING: " +#define PREFIX_INFO " INFO: " +#define PREFIX_ERROR " ERROR: " + +/** + * This array describes a log levels priorities with message prefix . + */ +extern struct log_level_info log_level_infos[]; + +/** + * This global variable holds the name of binary file an application has been + * executed from. + */ +extern char *progname; + +/** + * @brief Reads integer value from a text file. + * + * This function assumes that the only text in a file is requested number to + * read. In case of an error while reading from file the function will return + * a value stored in defval argument. + * + * @param[in] path location of a file to be read. + * @param[in] defval default value to return in case the file + * does not exist. + * @param[in] name name of a file to be read. + * + * @return Value read from a file if successful, otherwise a value stored in + * defval argument. + */ +int get_int(const char *path, int defval, const char *name); + +/** + * @brief Reads 64-bit unsigned integer from a text file. + * + * This function assumes that the only text in a file is requested number to + * read. In case of an error while reading from file the function will return + * a value stored in defval argument. + * + * @param[in] path Path where a file is located. + * @param[in] defval Default value to be returned in case of error. + * @param[in] name Name of a file to be read. + * + * @return Value read from a file if successful, otherwise a value stored in + * defval argument. + */ +uint64_t get_uint64(const char *path, uint64_t defval, const char *name); + +/** + * @brief Reads a content of a text file. + * + * This function reads a text file and return pointer to memory where the text + * has been stored. The memory allocated by the function must be release as soon + * as application does not require the content. Use free() function to give + * allocated memory back to the system. + * + * @param[in] path Path where a file is located. + * @param[in] name Name of a file to be read. + * + * @return Pointer to memory buffer if successful, otherwise NULL pointer. + */ +char *get_text(const char *path, const char *name); + +/** + * @brief Reads boolean value from a text file. + * + * This function assumes that the only text in a file is the requested value to + * read. The recognized boolean values are in the format 'Y' for True and 'N' + * for False. In case of an error while reading from file the function will + * return a value stored in defval argument. + * + * @param[in] path Path where a file is located. + * @param[in] defval Default value to be returned in case of error. + * @param[in] name Name of a file to be read. + * + * @return Value read from a file if successful, otherwise a value stored in + * defval argument. 1 is returned for True, 0 for False. + */ +int get_bool(const char *path, int defval, const char *name); + +/** + * @brief Writes a text to file. + * + * This function writes a text to a file and return the number of bytes written. + * If the file does not exist or the value is incorrect the function returns -1 + * and errno has additional error information. + * + * @param[in] path Location of file to write. + * @param[in] name Name of file to write. + * @param[in] value Text to write to a file. + * + * @return The number of bytes written or -1 if an error occurred. + */ +int put_text(const char *path, const char *name, const char *value); + +/** + * @brief Writes an integer value to a text file. + * + * This function writes an integer value to a text file. If the file does not + * exist or the value is out of range the function returns -1 and errno variable + * has additional error information. + * + * @param[in] path Location of file to write. + * @param[in] name Name of file to write. + * @param[in] value Integer value to write to a file. + * + * @return The number of bytes written or -1 if an error occurred. + */ +int put_int(const char *path, const char *name, int value); + +/** + * @brief Scans directory for files. + * + * This function reads a directory specified in path argument and puts found + * file on a list. The function puts a canonical paths on the list, however it + * does not resolve any symbolic link. + * + * This function allocates memory for the list elements. The caller should free + * it using list_erase(). + * + * @param[in] path Path to directory to read. + * @param[in] result Pointer to list where the directory contents + * will be put. + * + * @return 0 on success, -1 on error. + */ +int scan_dir(const char *path, struct list *result); + +/** + * @brief Writes a text to file. + * + * This function writes content of text buffer to file. If the file does not + * exist or a content is invalid the function returns -1 and errno variable has + * additional error information. + * + * @param[in] path Location and name of file to write to. + * @param[in] buf Pointer to text buffer to write to a file. + * + * @return Number of bytes written if successful, otherwise -1 for an error. + */ +ssize_t buf_write(const char *path, const char *buf); + +/** + * @brief Reads the content of a text file. + * + * The function reads the content of a text file and stores it to memory buffer + * allocated. The function determines a size of the file and allocates required + * amount of memory. User is required to free allocated memory as soon as + * application does not require the content. Use free() function to give memory + * back to the system pool. The function replaces last end-of-line character + * with '\0' character. + * + * @param[in] path Path and name of file to read. + * + * @return Pointer to memory block if successful, otherwise NULL pointer. + */ +char *buf_read(const char *path); + +/** + * @brief Gets major and minor of device. + * + * The function reads from text buffer the major and minor of block device. + * + * @param[in] buf Pointer to text buffer to interpret. + * @param[out] did Placeholder where major and minor will be + * stored. + * + * @return The function does not return a value. + */ +void get_id(const char *buf, struct device_id *did); + +/** + * @brief Open a local log file. + * + * The function opens a file to write log messages. If the given file does not + * exist the new one will be created. If the file already exist the file will be + * opened in append mode and the pointer will be set at the end of a file. + * + * @param[in] path Location and name of a log file. + * + * @return The function returns 0 if successful, otherwise -1 and errno variable + * has additional error information. + */ +int log_open(const char *path); + +/** + * @brief Close a local log file. + * + * The function closes a local log file. If the file has not been opened the + * function does nothing. + * + * @return The function does not return a value. + */ +void log_close(void); + +/** + * @brief Logs an message with given loglevel. + * + * The function logs a message at given level of verbosity. + * + * @param[in] loglevel Level of verbosity for a message. + * @param[in] buf Buffer containing format of a message. + * @param[in] ... Additional arguments according to format of + * a message. + * + * @return The function does not return a value. + */ +void _log(enum log_level_enum loglevel, const char *buf, ...); + +#define log_error(buf, ...) _log(LOG_LEVEL_ERROR, buf, ##__VA_ARGS__) +#define log_debug(buf, ...) _log(LOG_LEVEL_DEBUG, buf, ##__VA_ARGS__) +#define log_info(buf, ...) _log(LOG_LEVEL_INFO, buf, ##__VA_ARGS__) +#define log_warning(buf, ...) _log(LOG_LEVEL_WARNING, buf, ##__VA_ARGS__) +/** + */ +void set_invocation_name(char *invocation_name); + +/** + * @brief Copies a text buffer. + * + * This function copies source text buffer to destination buffer. The function + * always return a null-terminated buffer even if src does not fit in dest. + * + * @param[out] dest Pointer to destination buffer. + * @param[in] src Pointer to source buffer. + * @param[in] size Capacity of destination buffer in bytes. + * + * @return Pointer to destination buffer even if function failed. + */ +char *str_cpy(char *dest, const char *src, size_t size); + +/** + * @brief Duplicates a text buffer. + * + * This function duplicates a text buffer. It allocates a new memory block and + * copies the content of source buffer to new location. If pointer to source + * buffer is NULL the function will return NULL, too. The caller is required to + * free allocated memory as soon as content is not needed. + * + * @param[in] src Source buffer to duplicate the content. + * + * @return Pointer to allocated memory block if successful, otherwise NULL. + */ +char *str_dup(const char *src); + +/** + */ +char *truncate_path_component_rev(const char *path, int index); + +/** + */ +char *get_path_component_rev(const char *path, int index); + +/** + * @brief Extracts the 'hostX' part from path. + */ +char *get_path_hostN(const char *path); + +int match_string(const char *string, const char *pattern); + +/** + */ +int get_log_fd(void); + +/** + */ +void print_opt(const char *long_opt, const char *short_opt, const char *desc); + +/** + */ +status_t set_log_path(const char *path); + +/** + * Internal enumeration type. It is used to help parse command line arguments. + */ +enum opt { + OPT_ALL, + OPT_CONFIG, + OPT_DEBUG, + OPT_ERROR, + OPT_HELP, + OPT_INFO, + OPT_INTERVAL, + OPT_LOG, + OPT_QUIET, + OPT_VERSION, + OPT_WARNING, + OPT_LOG_LEVEL, + OPT_LIST_CTRL, + OPT_LISTED_ONLY, + OPT_FOREGROUND, + OPT_NULL_ELEMENT +}; + +extern struct option longopt_all[]; +void setup_options(struct option **longopt, char **shortopt, int *options, + int options_nr); +int get_option_id(const char *optarg); +status_t set_verbose_level(int log_level); + +const char *ibpi2str(enum ibpi_pattern ibpi); + +#endif /* _UTILS_H_INCLUDED_ */ diff --git a/src/version.h b/src/version.h new file mode 100644 index 0000000..4cb2047 --- /dev/null +++ b/src/version.h @@ -0,0 +1,39 @@ +/* + * Intel(R) Enclosure LED Utilities + * Copyright (C) 2009-2019 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#ifndef _VERSION_H_INCLUDED_ +#define _VERSION_H_INCLUDED_ + +/** + * The major number of LEDCTL utilities package. This number may be overwrite + * in makefile to reflect current version number. + */ +#ifndef VERSION_MAJOR +#define VERSION_MAJOR 0 +#endif + +/** + * The minor number of LEDCTL utilities package. This number may be overwrite + * in makefile to reflect current version number. + */ +#ifndef VERSION_MINOR +#define VERSION_MINOR 94 +#endif + +#endif /* _VERSION_H_INCLUDED_ */ diff --git a/src/vmdssd.c b/src/vmdssd.c new file mode 100644 index 0000000..d4d3d28 --- /dev/null +++ b/src/vmdssd.c @@ -0,0 +1,168 @@ +/* + * Intel(R) Enclosure LED Utilities + * Copyright (c) 2016-2019, Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "list.h" +#include "pci_slot.h" +#include "status.h" +#include "sysfs.h" +#include "utils.h" +#include "vmdssd.h" + +#define ATTENTION_OFF 0xF /* (1111) Attention Off, Power Off */ +#define ATTENTION_LOCATE 0x7 /* (0111) Attention Off, Power On */ +#define ATTENTION_REBUILD 0x5 /* (0101) Attention On, Power On */ +#define ATTENTION_FAILURE 0xD /* (1101) Attention On, Power Off */ + +#define SYSFS_PCIEHP "/sys/module/pciehp" + +static char *get_slot_from_syspath(char *path) +{ + char *cur, *ret = NULL; + char *temp_path = str_dup(path); + + cur = strtok(temp_path, "/"); + while (cur != NULL) { + char *next = strtok(NULL, "/"); + + if ((next != NULL) && strcmp(next, "nvme") == 0) + break; + cur = next; + } + + cur = strtok(cur, "."); + if (cur) + ret = str_dup(cur); + free(temp_path); + + return ret; +} + +static void get_ctrl(enum ibpi_pattern ibpi, uint16_t *new) +{ + switch (ibpi) { + case IBPI_PATTERN_LOCATE: + *new = ATTENTION_LOCATE; + break; + case IBPI_PATTERN_FAILED_DRIVE: + *new = ATTENTION_FAILURE; + break; + case IBPI_PATTERN_REBUILD: + *new = ATTENTION_REBUILD; + break; + default: + *new = ATTENTION_OFF; + break; + } +} + +static int check_slot_module(const char *slot_path) +{ + char module_path[PATH_MAX], real_module_path[PATH_MAX]; + struct list dir; + + // check if slot is managed by pciehp driver + snprintf(module_path, PATH_MAX, "%s/module", slot_path); + if (scan_dir(module_path, &dir) == 0) { + list_erase(&dir); + if (realpath(module_path, real_module_path) == NULL) + return -1; + if (strcmp(real_module_path, SYSFS_PCIEHP) != 0) + __set_errno_and_return(EINVAL); + } else { + __set_errno_and_return(ENOENT); + } + + return 0; +} + +struct pci_slot *vmdssd_find_pci_slot(char *device_path) +{ + char *pci_addr; + struct pci_slot *slot = NULL; + + pci_addr = get_slot_from_syspath(device_path); + if (!pci_addr) + return NULL; + + list_for_each(sysfs_get_pci_slots(), slot) { + if (strcmp(slot->address, pci_addr) == 0) + break; + slot = NULL; + } + free(pci_addr); + if (slot == NULL || check_slot_module(slot->sysfs_path) < 0) + return NULL; + + return slot; +} + +int vmdssd_write(struct block_device *device, enum ibpi_pattern ibpi) +{ + char attention_path[PATH_MAX]; + char buf[WRITE_BUFFER_SIZE]; + uint16_t val; + struct pci_slot *slot; + char *short_name = strrchr(device->sysfs_path, '/'); + + if (short_name) + short_name++; + else + short_name = device->sysfs_path; + + if (ibpi == device->ibpi_prev) + return 0; + + if ((ibpi < IBPI_PATTERN_NORMAL) || (ibpi > IBPI_PATTERN_LOCATE_OFF)) + __set_errno_and_return(ERANGE); + + slot = vmdssd_find_pci_slot(device->sysfs_path); + if (!slot) { + log_debug("PCI hotplug slot not found for %s\n", short_name); + __set_errno_and_return(ENODEV); + } + + log_debug("%s before: 0x%x\n", short_name, + get_int(slot->sysfs_path, 0, "attention")); + + get_ctrl(ibpi, &val); + snprintf(buf, WRITE_BUFFER_SIZE, "%u", val); + snprintf(attention_path, PATH_MAX, "%s/attention", slot->sysfs_path); + if (buf_write(attention_path, buf) != (ssize_t) strlen(buf)) { + log_error("%s write error: %d\n", slot->sysfs_path, errno); + return -1; + } + + log_debug("%s after: 0x%x\n", short_name, + get_int(slot->sysfs_path, 0, "attention")); + + return 0; +} + +char *vmdssd_get_path(const char *cntrl_path) +{ + return str_dup(cntrl_path); +} diff --git a/src/vmdssd.h b/src/vmdssd.h new file mode 100644 index 0000000..4c90fcb --- /dev/null +++ b/src/vmdssd.h @@ -0,0 +1,30 @@ +/* + * Intel(R) Enclosure LED Utilities + * Copyright (c) 2016-2019, Intel Corporation + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#ifndef _VMDSSD_H +#define _VMDSSD_H + +#include "block.h" +#include "ibpi.h" + +int vmdssd_write(struct block_device *device, enum ibpi_pattern ibpi); +char *vmdssd_get_path(const char *cntrl_path); +struct pci_slot *vmdssd_find_pci_slot(char *device_path); + +#endif diff --git a/systemd/Makefile.am b/systemd/Makefile.am new file mode 100644 index 0000000..962b122 --- /dev/null +++ b/systemd/Makefile.am @@ -0,0 +1,29 @@ +# +# Intel(R) Enclosure LED Utilities +# Copyright (C) 2009-2019 Intel Corporation. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms and conditions of the GNU General Public License, +# version 2, as published by the Free Software Foundation. +# +# This program is distributed in the hope 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. +# + +# Installation directory of ledmon systemd service unit. +systemddir = @SYSTEMD_PATH@ +SED = sed + +CLEANFILES = ledmon.service ledmon.service.tmp + +systemd_DATA = ledmon.service + +ledmon.service : ledmon.service.in + $(SED) -e 's|@sbindir[@]|$(sbindir)|g' < $< > $@.tmp + mv $@.tmp $@ diff --git a/systemd/ledmon.service.in b/systemd/ledmon.service.in new file mode 100644 index 0000000..059fd95 --- /dev/null +++ b/systemd/ledmon.service.in @@ -0,0 +1,11 @@ +[Unit] +Description=Enclosure LED Utilities + +[Install] +WantedBy=multi-user.target + +[Service] +Type=simple +User=root +ExecStart=@sbindir@/ledmon --foreground +Restart=on-failure