From 984d8f39dcc84eb01b7daf85de50f4026f342528 Mon Sep 17 00:00:00 2001 From: Evgeny Zinoviev Date: Wed, 29 May 2019 16:41:40 +0300 Subject: initial --- Makefile | 38 ++++++++ README.md | 45 ++++++++++ smc.c | 156 ++++++++++++++++++++++++++++++++ smc.h | 45 ++++++++++ smctool.c | 298 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 582 insertions(+) create mode 100644 Makefile create mode 100644 README.md create mode 100644 smc.c create mode 100644 smc.h create mode 100644 smctool.c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ce7cca2 --- /dev/null +++ b/Makefile @@ -0,0 +1,38 @@ +## +## Makefile for smctool +## +## Copyright (C) 2019 Evgeny Zinoviev +## +## 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; version 2 of the License. +## +## 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. +## + +CC = gcc +CFLAGS = -O2 -Wall -W +PROGRAM = smctool +INSTALL = /usr/bin/env install +PREFIX = /usr/local + +all: $(PROGRAM) + +$(PROGRAM): smc.o smctool.o + $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) + +install: $(PROGRAM) + $(INSTALL) $(PROGRAM) $(PREFIX)/sbin + +clean: + rm -f *.o $(PROGRAM) + +distclean: clean + +%.o: %.c + $(CC) $(CFLAGS) -c $^ -I. -o $@ + +.PHONY: all install clean distclean diff --git a/README.md b/README.md new file mode 100644 index 0000000..13c10a9 --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +# smctool + +A Linux userspace tool to read Apple SMC keys. Supports various data types and output formats. +This tool is a part of the coreboot project. + +### Usage + +##### Main options + +``` +-h, --help: print help +-k, --key : key name +-t, --type : data type, see below +``` + +##### Output format options: +``` +--output-hex: print value as a hexadecimal number +--output-bin: print binary representation +``` + +##### Supported data types +`ui8`, `ui16`, `ui32`, `si8`, `si16`, `flag`, `fpXY`, `spXY` + +**fp** and **sp** are unsigned and signed fixed point data types respectively. +The `X` in **fp** and **sp** data types is integer bits count and `Y` is fraction bits count. + +For example: `fpe2` means 14 integer bits, 2 fraction bits, `sp78` means 7 integer bits, 8 fraction bits (and one sign bit). + +### Examples + +Reading battery level: +``` +smctool -k B0FC -t ui16 # returns Full Capacity of Battery 0 +smctool -k B0RM -t ui16 # returns Remaining Capacity of Batery 0 +``` + +Reading fan speed: +``` +smctool -k F0Ac -t fpe2 +``` + +### License + +GPLv2 diff --git a/smc.c b/smc.c new file mode 100644 index 0000000..a875b2c --- /dev/null +++ b/smc.c @@ -0,0 +1,156 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2019 Evgeny Zinoviev + * + * SMC interacting code is based on applesmc linux driver by: + * Copyright (C) 2007 Nicolas Boichat + * Copyright (C) 2010 Henrik Rydberg + * + * 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; version 2 of the License. + * + * 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. + */ + +#include +#include +#include +#include +#include "smc.h" + +/* + * wait_read - Wait for a byte to appear on SMC port. Callers must + * hold applesmc_lock. + */ +int wait_read(void) +{ + uint8_t status; + int us; + for (us = APPLESMC_MIN_WAIT; us < APPLESMC_MAX_WAIT; us <<= 1) { + usleep(us); + status = inb(APPLESMC_CMD_PORT); + + /* read: wait for smc to settle */ + if (status & 0x01) + return 0; + } + + fprintf(stderr, "%s() fail: 0x%02x\n", __func__, status); + return -EIO; +} + +/* + * send_byte - Write to SMC port, retrying when necessary. Callers + * must hold applesmc_lock. + */ +int send_byte(uint8_t cmd, uint16_t port) +{ + uint8_t status; + int us; + + outb(cmd, port); + for (us = APPLESMC_MIN_WAIT; us < APPLESMC_MAX_WAIT; us <<= 1) { + usleep(us); + status = inb(APPLESMC_CMD_PORT); + /* write: wait for smc to settle */ + if (status & 0x02) + continue; + /* ready: cmd accepted, return */ + if (status & 0x04) + return 0; + /* timeout: give up */ + if (us << 1 == APPLESMC_MAX_WAIT) + break; + /* busy: long wait and resend */ + usleep(APPLESMC_RETRY_WAIT); + outb(cmd, port); + } + + fprintf(stderr, "%s(0x%02x, 0x%04x) fail: 0x%02x\n", + __func__, cmd, port, status); + return -EIO; +} + +int send_command(uint8_t cmd) +{ + return send_byte(cmd, APPLESMC_CMD_PORT); +} + +int send_argument(const char *key) +{ + int i; + + for (i = 0; i < 4; i++) + if (send_byte(key[i], APPLESMC_DATA_PORT)) + return -EIO; + return 0; +} + +int read_smc(uint8_t cmd, const char *key, uint8_t *buffer, uint8_t len) +{ + uint8_t status, data = 0; + int i; + + if (send_command(cmd) || send_argument(key)) { + fprintf(stderr, "%.4s: read arg fail\n", key); + return -EIO; + } + + /* This has no effect on newer (2012) SMCs */ + if (send_byte(len, APPLESMC_DATA_PORT)) { + fprintf(stderr, "%.4s: read len fail\n", key); + return -EIO; + } + + for (i = 0; i < len; i++) { + if (wait_read()) { + fprintf(stderr, "%.4s: read data[%d] fail\n", key, i); + return -EIO; + } + buffer[i] = inb(APPLESMC_DATA_PORT); + } + + /* Read the data port until bit0 is cleared */ + for (i = 0; i < 16; i++) { + usleep(APPLESMC_MIN_WAIT); + status = inb(APPLESMC_CMD_PORT); + if (!(status & 0x01)) + break; + data = inb(APPLESMC_DATA_PORT); + } + + if (i) + fprintf(stderr, "flushed %d bytes, last value is: %d\n", + i, data); + + return 0; +} + +int write_smc(uint8_t cmd, const char *key, const uint8_t *buffer, uint8_t len) +{ + int i; + + if (send_command(cmd) || send_argument(key)) { + fprintf(stderr, "%s: write arg fail\n", key); + return -EIO; + } + + if (send_byte(len, APPLESMC_DATA_PORT)) { + fprintf(stderr, "%.4s: write len fail\n", key); + return -EIO; + } + + for (i = 0; i < len; i++) { + if (send_byte(buffer[i], APPLESMC_DATA_PORT)) { + fprintf(stderr, "%s: write data fail\n", key); + return -EIO; + } + } + + return 0; +} diff --git a/smc.h b/smc.h new file mode 100644 index 0000000..dffd8db --- /dev/null +++ b/smc.h @@ -0,0 +1,45 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2019 Evgeny Zinoviev + * + * 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; version 2 of the License. + * + * 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. + */ + +#ifndef SMC_H +#define SMC_H + +#include +#include +#include + +/* data port used by Apple SMC */ +#define APPLESMC_DATA_PORT 0x300 +/* command/status port used by Apple SMC */ +#define APPLESMC_CMD_PORT 0x304 + +#define APPLESMC_MAX_DATA_LENGTH 32 + +/* wait up to 128 ms for a status change. */ +#define APPLESMC_MIN_WAIT 0x0010 +#define APPLESMC_RETRY_WAIT 0x0100 +#define APPLESMC_MAX_WAIT 0x20000 + +#define APPLESMC_READ_CMD 0x10 +#define APPLESMC_WRITE_CMD 0x11 + +int wait_read(void); +int send_byte(uint8_t cmd, uint16_t port); +int send_command(uint8_t cmd); +int send_argument(const char *key); +int read_smc(uint8_t cmd, const char *key, uint8_t *buffer, uint8_t len); +int write_smc(uint8_t cmd, const char *key, const uint8_t *buffer, uint8_t len); + +#endif /* SMC_H */ diff --git a/smctool.c b/smctool.c new file mode 100644 index 0000000..fda7904 --- /dev/null +++ b/smctool.c @@ -0,0 +1,298 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2019 Evgeny Zinoviev + * + * 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; version 2 of the License. + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "smc.h" +#include "smctool.h" + +static bool fp_bits(char c, uint8_t *dest) +{ + if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) { + *dest = strtol(&c, NULL, 16); + return true; + } + return false; +} + +static void print_bits(uint8_t len, uint8_t *ptr) +{ + for (int i = len - 1; i >= 0; i--) { + for (int j = 7; j >= 0; j--) + printf("%u", (ptr[i] >> j) & 1); + + if (i > 0) + printf(" "); + } +} + +static void print_usage(const char *name) +{ + printf("usage: %s \n", name); + printf("\n" + "Options:\n" + " -h, --help: print this help\n" + " -k, --key : key name\n" + " -t, --type : data type, see below\n" + " --output-hex\n" + " --output-bin\n" + "\n" + "Supported data types:\n" + " ui8, ui16, ui32, si8, si16, flag, fpXY, spXY\n" + "\n" + " fp and sp are unsigned and signed fixed point\n" + " data types respectively.\n" + "\n" + " The X in fp and sp data types is integer bits count\n" + " and Y is fraction bits count.\n" + "\n" + " For example,\n" + " fpe2 means 14 integer bits, 2 fraction bits,\n" + " sp78 means 7 integer bits, 8 fraction bits\n" + " (and one sign bit).\n" + "\n"); +} + + +enum output_format {dec, hex, bin}; + +enum smctype {none, ui8, ui16, ui32, si8, si16, flag, fp, sp}; +struct { + enum smctype type; + uint8_t len; + char *str; + char *decfmt; + char *hexfmt; +} types[] = { + {ui8, 1, "ui8", "%u", "0x%02x"}, + {ui16, 2, "ui16", "%u", "0x%04x"}, + {ui32, 4, "ui32", "%u", "0x%08x"}, + {si8, 1, "si8", "%d", "0x%02x"}, + {si16, 2, "si16", "%d", "0x%04x"}, + {flag, 1, "flag", "%u", "0x%01x"}, +}; + +int main(int argc, char *argv[]) +{ + bool show_help = false; + int opt, option_index = 0; + char name[KEYBUFSIZE+1] = {0}; + char typebuf[KEYBUFSIZE+1] = {0}; + uint8_t fp_int_bits = 0, fp_fraction_bits = 0; + + enum output_format of = dec; + char *ofmt = NULL; + char fmtbuf[32]; + + enum smctype type = none; + uint8_t len; + + struct option long_options[] = { + {"help", 0, 0, 'h'}, + {"key", 1, 0, 'k'}, + {"type", 1, 0, 't'}, + {"output-hex", 0, 0, 1000}, + {"output-bin", 0, 0, 1001}, + {0, 0, 0, 0} + }; + + if (argv[1] == NULL) { + print_usage(argv[0]); + exit(0); + } + + while ((opt = getopt_long(argc, argv, "hk:t:", + long_options, &option_index)) != EOF) { + switch (opt) { + case 'h': + show_help = true; + break; + + case 'k': + snprintf(name, KEYBUFSIZE, "%s", optarg); + break; + + case 't': + snprintf(typebuf, KEYBUFSIZE, "%s", optarg); + break; + + case 1000: // output-hex + of = hex; + break; + + case 1001: // output-bin + of = bin; + break; + } + } + + if (optind < argc) { + fprintf(stderr, "Error: Extra parameter found.\n"); + print_usage(argv[0]); + exit(1); + } + + if (show_help) { + print_usage(argv[0]); + exit(0); + } + + /* Validate key name */ + if (strlen(name) != 4) { + fprintf(stderr, "Key name must be 4 characters long.\n"); + exit(1); + } + + /* Validate key type */ + if ((typebuf[0] == 'f' || typebuf[0] == 's') && typebuf[1] == 'p') { + if (!fp_bits(typebuf[2], &fp_int_bits) + || !fp_bits(typebuf[3], &fp_fraction_bits)) { + fprintf(stderr, "Invalid fixed point data type.\n"); + exit(1); + } + if (typebuf[0] == 'f' && fp_int_bits + fp_fraction_bits != 16) { + fprintf(stderr, + "Invalid unsigned fixed point data type.\n"); + exit(1); + } + if (typebuf[0] == 's' && fp_int_bits + fp_fraction_bits != 15) { + fprintf(stderr, + "Invalid signed fixed point data type.\n"); + exit(1); + } + type = typebuf[0] == 'f' ? fp : sp; + len = 2; + ofmt = "%d.%u"; + } + + if (type == none) { + for (unsigned int i = 0; i < ARRAY_SIZE(types); i++) { + if (strcmp(typebuf, types[i].str)) + continue; + + type = types[i].type; + len = types[i].len; + if (of == dec) + ofmt = types[i].decfmt; + else if (of == hex) + ofmt = types[i].hexfmt; + break; + } + } + + if (type == none) { + fprintf(stderr, "Key type \"%s\" is not known.\n", typebuf); + exit(1); + } + + /* Check permissions */ + if (geteuid() != 0) { + fprintf(stderr, "You must be root.\n"); + exit(1); + } + + /* Open SMC port */ + if (ioperm(APPLESMC_DATA_PORT, 0x10, 1)) { + fprintf(stderr, "ioperm: %s\n", strerror(errno)); + exit(1); + } + + /* Read key from SMC */ + int retval; + uint32_t buf = 0; + + retval = read_smc(APPLESMC_READ_CMD, name, (uint8_t *)&buf, len); + assert(retval == 0); + + /* Handle returned value according to the requested type */ + buf = be32toh(buf); + + int32_t fp_int = 0; + uint16_t fp_fraction = 0; + bool fp_sign = false; + + switch (type) { + case flag: + buf = (buf >> 24) & 1; + break; + + case ui8: + buf = (buf >> 24) & 0xff; + break; + + case si8: + buf = (buf >> 24) & 0xff; + if (of == dec) + buf = (int8_t)buf; + break; + + case ui16: + buf = (buf >> 16) & 0xffff; + break; + + case si16: + buf = (buf >> 16) & 0xffff; + if (of == dec) + buf = (int16_t)buf; + break; + + case fp: + buf = (buf >> 16) & 0xffff; + fp_int = buf >> fp_fraction_bits; + fp_fraction = buf & (0xffff >> fp_int_bits); + break; + + case sp: + buf = (buf >> 16) & 0xffff; + fp_sign = ((buf >> 15) & 1) == 1; + + buf &= ~(1 << 15); + + fp_int = buf >> fp_fraction_bits; + fp_fraction = buf & (0xffff >> fp_int_bits); + + if (fp_sign) + fp_int *= -1; + break; + + case ui32: + case none: // to avoid gcc warning + break; + } + + /* Output the result */ + if (of != bin) { + strcpy(fmtbuf, "%s = "); + strcat(fmtbuf, ofmt); + strcat(fmtbuf, "\n"); + + if (type == fp || type == sp) + printf(fmtbuf, name, fp_int, fp_fraction); + else + printf(fmtbuf, name, buf); + } else { + printf("%s = ", name); + print_bits(len, (uint8_t *)&buf); + printf("\n"); + } + + return 0; +} -- cgit v1.2.3