[NARAYAN]

UnThinkPad


I have a Thinkpad. It's great, it works, brilliant 👍. But the ThinkPad is a prophesied machine of untapped potential and holds the key to attaining true freedom.

The following code and examples I write will be only for Linux. Windows users don't get much love from me.

The ThinkLight 💡

A relic from a bygone era.

Let's make it speak morse! First some basic understanding of how this would work:

For this we'll make use of the ec_sys kernel module. This is a Linux kernel module that provides a sysfs interface to the Embedded Controller, which is a microcontroller responsible for controlling tasks related to battery, thermals, and in our case... the lights.

A good place to start would be by loading the module onto the kernel.

sudo modprobe -r ec_sys # incase its already loaded, unload it
sudo modprobe ec_sys write_support=1

Controlling the LED

Now manipulating the LED state should be as simple as writing to /sys/kernel/debug/ec/io.

# Switches off the LED
echo -n -e \x0a | dd of="/sys/kernel/debug/ec/ec0/io" bs=1 seek=12 count=1 conv=notrunc 2> /dev/null

The value we write to the file is a hexadecimal number.

  • The first nibble represents the state:
    • 0 : off
    • 8 : on
    • c : blink
  • The second nibble represents the LED in focus. Here are some from my tests:
    • a : The red led on the thinkpad logo.
    • e : The mute led
    • 6 : Capslock LED
    • 0 : Power LED
    • There may be more I didn't bother finding (lol).

Extending this concept programatically, we can write the following C script...

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

#define EC_IO_PATH "/sys/kernel/debug/ec/ec0/io"
#define ON_BYTE 0x80
#define OFF_BYTE 0x00
#define LED_OFFSET 12

void run_command(const char *cmd) {
    if (system(cmd) != 0) {
        perror("Command failed");
    }
}

void write_led(unsigned char value) {
    int fd = open(EC_IO_PATH, O_WRONLY);
    if (fd < 0) {
        perror("Failed to open EC interface");
        exit(EXIT_FAILURE);
    }

    if (lseek(fd, LED_OFFSET, SEEK_SET) < 0) {
        perror("Seek failed");
        close(fd);
        exit(EXIT_FAILURE);
    }

    if (write(fd, &value, 1) != 1) {
        perror("Write failed");
    }

    close(fd);
}

void dit() {
    write_led(ON_BYTE);
    usleep(100000);
    write_led(OFF_BYTE);
    usleep(100000);
}

void dah() {
    write_led(ON_BYTE);
    usleep(300000);
    write_led(OFF_BYTE);
    usleep(100000);
}

void morse(char c) {
    switch (c) {
        case '0': dah(); dah(); dah(); dah(); dah(); break;
        case '1': dit(); dah(); dah(); dah(); dah(); break;
        case '2': dit(); dit(); dah(); dah(); dah(); break;
        case '3': dit(); dit(); dit(); dah(); dah(); break;
        case '4': dit(); dit(); dit(); dit(); dah(); break;
        case '5': dit(); dit(); dit(); dit(); dit(); break;
        case '6': dah(); dit(); dit(); dit(); dit(); break;
        case '7': dah(); dah(); dit(); dit(); dit(); break;
        case '8': dah(); dah(); dah(); dit(); dit(); break;
        case '9': dah(); dah(); dah(); dah(); dit(); break;
        case 'a': dit(); dah(); break;
        case 'b': dah(); dit(); dit(); dit(); break;
        case 'c': dah(); dit(); dah(); dit(); break;
        case 'd': dah(); dit(); dit(); break;
        case 'e': dit(); break;
        case 'f': dit(); dit(); dah(); dit(); break;
        case 'g': dah(); dah(); dit(); break;
        case 'h': dit(); dit(); dit(); dit(); break;
        case 'i': dit(); dit(); break;
        case 'j': dit(); dah(); dah(); dah(); break;
        case 'k': dah(); dit(); dah(); break;
        case 'l': dit(); dah(); dit(); dit(); break;
        case 'm': dah(); dah(); break;
        case 'n': dah(); dit(); break;
        case 'o': dah(); dah(); dah(); break;
        case 'p': dit(); dah(); dah(); dit(); break;
        case 'q': dah(); dah(); dit(); dah(); break;
        case 'r': dit(); dah(); dit(); break;
        case 's': dit(); dit(); dit(); break;
        case 't': dah(); break;
        case 'u': dit(); dit(); dah(); break;
        case 'v': dit(); dit(); dit(); dah(); break;
        case 'w': dit(); dah(); dah(); break;
        case 'x': dah(); dit(); dit(); dah(); break;
        case 'y': dah(); dit(); dah(); dah(); break;
        case 'z': dah(); dah(); dit(); dit(); break;
        case ' ': usleep(600000); break;
    }
    usleep(200000);
}

void parse(const char *input) {
    while (*input) {
        morse(*input);
        input++;
    }
}

int main() {
    char input[100];

    run_command("modprobe -r ec_sys");
    run_command("modprobe ec_sys write_support=1");

    write_led(OFF_BYTE);

    printf("Enter a word: ");
    if (fgets(input, sizeof(input), stdin) == NULL) {
        perror("Error reading input");
        return EXIT_FAILURE;
    }

    input[strcspn(input, "\n")] = '\0'; // Remove newline from input

    printf("Blinking \"%s\"\n", input);
    parse(input);

    sleep(1);
    write_led(ON_BYTE);

    run_command("modprobe -r ec_sys");

    return 0;
}