diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..cb68e3c --- /dev/null +++ b/Makefile.am @@ -0,0 +1,14 @@ +DISTCHECK_CONFIGURE_FLAGS = \ + --with-systemdsystemunitdir=$$dc_install_base/$(systemdsystemunitdir) + +if HAVE_SYSTEMD +systemdsystemunit_DATA = \ + init-headphone.service +endif + +sbin_SCRIPTS = src/init-headphone + +CLEANFILES = init-headphone.service + +init-headphone.service: init-headphone.service.in Makefile + sed -e 's,$${sbindir},${sbindir},g' init-headphone.service.in > init-headphone.service diff --git a/NEWS.md b/NEWS.md new file mode 100644 index 0000000..322d22c --- /dev/null +++ b/NEWS.md @@ -0,0 +1,58 @@ +# Changes in 0.12 + + * add Autotools + * improve logging + +# Changes in 0.11 + + * remove list of supported models + * show error message if library not found + * improve logging + * show active default effect in help + * hide --force argument + * try to load missing kernel modules + * add configuration examples + +# Changes in 0.10 + + * recognize more i2c bus names + * add license + +# Changes in 0.9 + + * remove model check + +# Changes in 0.8 + + * add ability to skip model check + * only print exception in verbose mode + +# Changes in 0.7 + + * use baseboard product name instead of system product name to identify model + * model communication with device after newer versions of the Windows driver + * add lots of supposedly supported models + * improved help + * improved logging + +# Changes in 0.6 + + * add Eurocom M4 to supported systems + +# Changes in 0.5 + + * add W230SD to supported systems + +# Changes in 0.4 + + * add support for Python 3 (still works with Python 2) + * python-smbus is no longer required + * better error handling + +# Changes in 0.3 + + * add "Mythlogic Chaos 1313-A" and "HUMA H3" to supported products + * add mute/unmute and some effects + +# Changes in 0.2 +Initial release diff --git a/README.md b/README.md index 774fd38..41dfb84 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,36 @@ -# init-headphone-ubuntu +# init-headphone Manage the headphone amplifier found in some Clevo laptops. Can initialize the device if headphones are not working after suspend. -**Ubuntu package for [init-headphone](https://github.com/Unrud/init-headphone)** +**There are packages for +[Arch Linux](https://aur.archlinux.org/packages/init-headphone/), +[Fedora](https://github.com/letitz/init-headphone/releases) and +[Ubuntu](https://github.com/Unrud/init-headphone-ubuntu/releases)** + +## Installation + +To install just run: + + ./autogen.sh + ./configure + make + make install + +If **systemd** is available, a unit file that starts the program automatically +gets installed. To enable it run: + + systemctl enable init-headphone + +If you are not using **systemd**, take a look at the *etc/* folder. It +includes example configuration for **pm-utils** and **upstart** to start the +program automatically. + +On older Linux versions you might have to add the kernel parameter +``acpi_enforce_resources=lax`` to make the i2c driver work. ## Usage ``` -$ init-headphone --help +init-headphone --help usage: init-headphone [-h] [--version] [-v] [command] Manage the headphone amplifier found in some Clevo laptops @@ -34,8 +58,91 @@ available commands: recovery ``` +## Troubleshoot + + * Run **init-headphone** with the ``--verbose`` argument. + * Use ``dmesg | grep i801`` to check for problems with the SMBus driver. + * List all i2c busses with ``i2cdetect -l`` to find the full name of the bus + **SMBus I801 adapter...**. Use that name as a argument for **i2cdetect** + like ``i2cdetect "SMBus I801 adapter at f040"``. This should show the + headphone amplifier at address 0x73. + +## Information about the Windows driver + +The Windows driver consists of a kernel space component **SvThANSP.sys** and a +user space component **hp.dll**. +The kernel driver is simple and only supports a few commands that allow access +to I/O ports. +**hp.dll** uses [DeviceIoControl](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363216%28v=vs.85%29.aspx) +to communicates via the device file ``\\.\SvANSPDo`` with the driver. +It talks directly to the SMBus controller. + +**hp.dll** exports the functions ``InitHeadphone()``, ``Set_Mute(bool)`` and +``Set_effect(int)`` to control the headphone amplifier which is connected to +the SMBus. + +If you are interested in analyzing or running the Windows driver on Linux +(with [Wine](https://winehq.org)) take a look at: https://github.com/Unrud/init-headphone-tools + +### Supported ``dwIoControlCode``s for [DeviceIoControl](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363216%28v=vs.85%29.aspx) + +#### 0x9C402494: Enumerate PCI device + +``lpInBuffer`` amd ``lpOutBuffer`` look something like: + +```c +struct { + int bus, + int device, + int func, + int pcireg, + int result, + int unused +} +``` + +The driver reads a word from the register ``pcireg`` of the with +``bus``, ``device`` and ``func`` specified PCI device. +The result is returned in ``result``. If the register doesn't exist, the +returned result is 0xffff. It's used to find the SMBus controller by +**hp.dll**. + +#### 0x9C4024D0: Read byte + +``lpInBuffer`` amd ``lpOutBuffer`` look something like: + +```c +struct { + int address, + int data_read, + int data_write +} +``` + +The driver reads one byte from ``address``. The result is returned in +``data_read``. + +#### 0x9C4024C4: Write byte + +``lpInBuffer`` amd ``lpOutBuffer`` are the same as above. + +The driver writes one byte ``data_write`` to ``address``. + +### SMBus controller + +A detailed description of the controller is available in the +[chipset datasheet](https://www-ssl.intel.com/content/dam/www/public/us/en/documents/datasheets/8-series-chipset-pch-datasheet.pdf). + +The important registers are: + + * **Transmit Slave Address Register**: Bit 0 indicates direction, the other + 7 Bits are the device address (Offset: 0x4) + * **Host Command Register**: Command (Offset: 0x3) + * **Host Data 0 Register**: Data (Offset: 0x5) + ## Supported models -This list is subject to change. If the headphone jack is not working after suspend, the model is probably supported. +This list is subject to change. If the headphone jack is not working after +suspend, the model is probably supported. ```x is used as wildcard``` * N151SD/N155SD/N170SD/N150SC/N151SC/N155SC @@ -77,4 +184,4 @@ This list is subject to change. If the headphone jack is not working after suspe * W970TUQ * WA50SBQ * WA50SFQ Series -* WA50SJQ Series \ No newline at end of file +* WA50SJQ Series diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..f409ade --- /dev/null +++ b/autogen.sh @@ -0,0 +1,8 @@ +#!/bin/sh +AUTORECONF="$(which autoreconf)" +if test -z "$AUTORECONF" +then + echo "*** Error: autoreconf not found" + exit 1 +fi +"$AUTORECONF" --force --install --verbose || exit $? diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..3e4b1c8 --- /dev/null +++ b/configure.ac @@ -0,0 +1,23 @@ +AC_INIT([init-headphone], [0.12]) + +PKG_PROG_PKG_CONFIG +AC_ARG_WITH([systemdsystemunitdir], + [AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd service files])],, + [with_systemdsystemunitdir=auto]) +AS_IF([test "x$with_systemdsystemunitdir" = "xyes" -o "x$with_systemdsystemunitdir" = "xauto"], [ + def_systemdsystemunitdir=$($PKG_CONFIG --variable=systemdsystemunitdir systemd) + + AS_IF([test "x$def_systemdsystemunitdir" = "x"], + [AS_IF([test "x$with_systemdsystemunitdir" = "xyes"], + [AC_MSG_ERROR([systemd support requested but pkg-config unable to query systemd package])]) + with_systemdsystemunitdir=no], + [with_systemdsystemunitdir="$def_systemdsystemunitdir"])]) +AS_IF([test "x$with_systemdsystemunitdir" != "xno"], + [AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir])]) +AM_CONDITIONAL([HAVE_SYSTEMD], [test "x$with_systemdsystemunitdir" != "xno"]) + +AM_INIT_AUTOMAKE([foreign]) + +AC_CONFIG_FILES([Makefile]) + +AC_OUTPUT diff --git a/debian/changelog b/debian/changelog index 92f4b70..dee2639 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +init-headphone (0.12.0) stable; + + * improve logging + + -- Unrud Wed, 22 Mar 2017 18:38:45 +0100 + init-headphone (0.11.0) stable; * show error message if library not found diff --git a/debian/init-headphone.service b/debian/init-headphone.service index 0af2c28..6a12c2e 100644 --- a/debian/init-headphone.service +++ b/debian/init-headphone.service @@ -1,5 +1,5 @@ [Unit] -Description=Initialize headphone amplifier found in some Clevo laptops +Description=Initialize headphone amplifier After=suspend.target After=hibernate.target After=hybrid-sleep.target diff --git a/debian/init-headphone.upstart b/debian/init-headphone.upstart index 0ae23e4..5c07dbf 100644 --- a/debian/init-headphone.upstart +++ b/debian/init-headphone.upstart @@ -1,4 +1,4 @@ -description "Initialize headphone amplifier found in some Clevo laptops" +description "Initialize headphone amplifier" start on runlevel [2345] diff --git a/etc/linux-systemd/init-headphone.service b/etc/linux-systemd/init-headphone.service index c982ec6..e14d051 100644 --- a/etc/linux-systemd/init-headphone.service +++ b/etc/linux-systemd/init-headphone.service @@ -1,5 +1,5 @@ [Unit] -Description=Initialize headphone amplifier found in some Clevo laptops +Description=Initialize headphone amplifier After=suspend.target After=hibernate.target After=hybrid-sleep.target diff --git a/init-headphone.service.in b/init-headphone.service.in new file mode 100644 index 0000000..04ff85e --- /dev/null +++ b/init-headphone.service.in @@ -0,0 +1,15 @@ +[Unit] +Description=Initialize headphone amplifier +After=suspend.target +After=hibernate.target +After=hybrid-sleep.target + +[Service] +Type=oneshot +ExecStart=${sbindir}/init-headphone + +[Install] +WantedBy=suspend.target +WantedBy=hibernate.target +WantedBy=hybrid-sleep.target +WantedBy=multi-user.target diff --git a/src/init-headphone b/src/init-headphone index e307221..41e1633 100755 --- a/src/init-headphone +++ b/src/init-headphone @@ -20,11 +20,12 @@ import ctypes import logging import os import subprocess +import sys import traceback __all__ = ["init", "set_mute", "set_effect", "recovery"] -VERSION = "0.11" +VERSION = "0.12" SUPPORTED_I2C_BUS_NAMES = ["SMBus I801 adapter"] I2C_CLASS_PATH = "/sys/class/i2c-dev" DEV_PATH = "/dev" @@ -116,7 +117,7 @@ class SMBus(object): err = self.__libc.ioctl(self.__fd, I2C_SLAVE, address) if err != 0: self.__logger.error("Can't set I2C slave address") - raise RuntimeError("Can't set I2C slave address") + raise OSError(err, os.strerror(err)) self.__address = address def __access(self, read_write, device_cmd, size, data): @@ -131,7 +132,7 @@ class SMBus(object): err = self.__libc.ioctl(self.__fd, I2C_SMBUS, ctypes.byref(args)) if err != 0: self.__logger.error("Can't transfer data on I2C bus") - raise RuntimeError("Can't transfer data on I2C bus") + raise OSError(err, os.strerror(err)) def write_byte_data(self, device_cmd, value): self.__logger.info("Writing byte data on I2C bus: " @@ -317,7 +318,35 @@ def main(): recovery() +class ColorStreamHandler(logging.StreamHandler): + def emit(self, record): + try: + FORMAT_SEQ = "\033[%dm" + fmt = 0 # reset + if record.levelno >= logging.ERROR: + fmt = 31 # red + elif record.levelno >= logging.WARNING: + fmt = 33 # yellow + msg = self.format(record) + if hasattr(self.stream, 'isatty') and self.stream.isatty(): + fs = (FORMAT_SEQ + "%s" + FORMAT_SEQ + "\n") % (fmt, msg, 0) + else: + fs = "%s\n" % msg + self.stream.write(fs) + self.flush() + except Exception: + self.handleError(record) + + +def setup_logging(): + ch = ColorStreamHandler(sys.stderr) + fmt = logging.Formatter('%(levelname)s:%(message)s') + ch.setFormatter(fmt) + logging.getLogger().addHandler(ch) + + if __name__ == "__main__": + setup_logging() try: main() except Exception: