I recently dug out an old KVM switch I was playing with back in 2005–the Linkskey LKU-UA02. It’s a compact two-output switch with two USB ports (keyboard and mouse), a VGA port, a speaker jack, and a microphone jack. It works great, but I was a little saddened after I got it and realized it had no Linux or Mac OS X drivers. It was still OK because it has a physical button for changing between which computer to control, but it also had a cool little Windows-only utility for controlling it through software or locking the audio output to a specific computer–and I couldn’t use any of those features from my Mac or a Linux machine.

In 2005 (a.k.a. my college days), I had attempted to write a control utility for it in OS X. I installed USB sniffing software on my Windows PC and recorded the USB traffic as I sent various commands through the Windows control program. I gave it my best shot, even going so far as to ask for a bit of help on Apple’s USB mailing list, but I never figured out how to get past a roadblock I was running into when opening the device in Apple’s I/O Kit. In hindsight, I was probably jumping into something just a little too complicated for my knowledge at the time. With that said, the best way to learn something in the programming world is to do it! It wasn’t wasted time; I definitely learned a lot about USB in the process. I actually believe that the device has something invalid in the descriptor for the endpoint I was using (a zero value for bInterval), and older versions of Mac OS X choked on that problem, so it may not have even been my fault–I think that was the reason I eventually gave up. In fact, if I look at the latest (as of this writing) version of AppleUSBOHCI_UIM.cpp, I see that it still complains if you try to create an interrupt endpoint with a polling rate of zero (it says, “that’s illegal!”), while the analogous UHCI controller code does not have this check.

When I stumbled upon the switch again last week, I decided to take another stab at it. This time, I decided to use libusb, which is better suited for the task given its simplicity compared to I/O Kit and its cross-platform compatibility. I ended up succeeding! Not only does it work in OS X, but it also works great in Linux and Windows 7.

The vendor ID of this KVM switch is 10d5 and the product ID is 000d. The chipset appears to be made by Uni Class, so it’s possible that other KVM switches use the exact same chipset (and thus may be compatible with my control software). In particular, some Googling seems to imply that the TRENDnet TK-407 has the exact same vendor and device ID, although it’s a 4-port KVM switch–I can see how the protocol would scale in that case to switch between four outputs, so I have left room in my code to allow switching between four outputs. Anyway, if you have a device with the same device and vendor ID, you should try it out!

Here is the code (just compile it with your favorite C compiler, remembering to link against libusb-1.0):

#include <libusb-1.0/libusb.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

// USB vendor and product ID of the KVM device
#define KVM_VENDOR_ID           0x10D5
#define KVM_DEVICE_ID           0x000D

bool is_kvm(libusb_device *dev);
void print_usage(char *argv[]);

int main(int argc, char *argv[]) {
    int exit_code = 0;

    // Make sure there is a single argument passed to the app
    if (argc != 2) {
        print_usage(argv);
        exit_code = 1;
        goto exit_err_early;
    }

    // Convert the argument to a number
    char *endptr;
    unsigned long index = strtoul(argv[1], &endptr, 10);
    if ((endptr == argv[1]) || (index > 3)) {
        print_usage(argv);
        exit_code = 1;
        goto exit_err_early;
    }

    // Initialize libusb
    int result;
    result = libusb_init(NULL);
    if (result) {
        fprintf(stderr, "Unable to initialize libusb.\n");
        exit_code = 1;
        goto exit_err_early;
    }

    // Find devices
    libusb_device **list;
    libusb_device *found = NULL;
    ssize_t cnt = libusb_get_device_list(NULL, &list);
    ssize_t i = 0;
    if (cnt < 0) {
        fprintf(stderr, "Unable to get list of USB devices.\n");
        exit_code = 1;
        goto exit_err_dev_list;
    }

    // Look for a match
    for (i = 0; i < cnt; i++) {
        libusb_device *device = list[i];
        if (is_kvm(device)) {
            found = device;
            break;
        }
    }

    // Did we find the KVM?
    if (found) {
        libusb_device_handle *handle;

        // Open the device...
        result = libusb_open(found, &handle);
        if (result) {
            fprintf(stderr, "Unable to open KVM device.\n");
            exit_code = 1;
            goto exit_error_open;
        }

        // Claim interface 1...
        result = libusb_claim_interface(handle, 1);
        if (result) {
            fprintf(stderr, "Unable to claim KVM interface.\n");
            exit_code = 1;
            goto exit_error_claim_interface;
        }

        // Send the interrupt transfer
        unsigned char transfer[8] = "\x01\x00\x00\x78\x01\x3B\x00\x00";
        transfer[1] = (unsigned char)index;
        int bytes_sent = 0;
        result = libusb_interrupt_transfer(handle,
            0x02 /* EP 2 OUT */,
            transfer,
            sizeof(transfer),
            &bytes_sent,
            0 /* no timeout */
        );

        // Check result of transfer
        if (result) {
            fprintf(stderr, "Unable to send KVM switch message.\n");
            exit_code = 1;
            goto exit_error_transfer;
        }

        // Print result
        printf("Switched to KVM output %lu.\n", index);

        // Clean up
exit_error_transfer:
        result = libusb_release_interface(handle, 1);
        if (result) {
            fprintf(stderr, "Error releasing interface 1.\n");
        }

exit_error_claim_interface:
        libusb_close(handle);
    } else {
        fprintf(stderr, "Unable to find KVM USB device.\n");
    }

exit_error_open:
    libusb_free_device_list(list, 1);
exit_err_dev_list:
    libusb_exit(NULL);
exit_err_early:
    return exit_code;
}

// Checks a libusb_device to see if it matches
bool is_kvm(libusb_device *dev) {
    // Grab the descriptor...
    struct libusb_device_descriptor desc;
    if (libusb_get_device_descriptor(dev, &desc) != 0) {
        fprintf(stderr, "Warning: Unable to get device descriptor.\n");
        return false;
    }

    // And see if the vendor/product ID matches
    if ((desc.idVendor == KVM_VENDOR_ID) &&
        (desc.idProduct == KVM_DEVICE_ID)) {
        return true;
    }

    // No match, so false
    return false;
}

// Prints usage info about the program
void print_usage(char *argv[]) {
    fprintf(stderr, "Usage: %s <index of KVM output (0-3)>\n", argv[0]);
}

I have noticed occasional error messages saying “Unable to send KVM switch message”, but despite the error, it still switches correctly. My guess is that the USB device disappears before I get a chance to completely close it. There may be a few small improvements needed in that regard, even if the improvement is just to always assume success at that point and not print an error message.

I plan on making a graphical interface (probably with Qt) for easy control in the future, but for now this command line utility works great! There are some extra features available in the KVM switch’s protocol such as automatically cycling between the outputs, fixing the audio port to a specific output, and determining which output is the currently active output. I haven’t implemented any of that extra protocol yet, but it shouldn’t be too difficult to do.

I do want to figure out how to do the equivalent task in I/O Kit at some point just for my own sanity and for some closure on the problem I had back in 2005, but it’s not high on my priority list. After my research into Apple’s open source code described above, I’m fairly sure that I still won’t be able to make it work on an older PowerPC computer at all. It will probably work OK with newer Intel computers, though. In fact, it would be interesting to test the libusb solution on a PowerPC computer to see if it works at all (my guess is it won’t work). So much to try and so little time!

After recently installing Ubuntu 11.10 onto my Mac mini, I’ve been mildly annoyed when I connect to it through PuTTY from my Windows machine. It’s working fine, except gcc displays a weird “â” character instead of quotes in its error messages. I figured it was some weird locale or terminal setting I hadn’t configured properly in Ubuntu because I did a minimal install with only a few server packages, but I was dead wrong. I tried SSHing to a standard Ubuntu 11.10 install with a regular desktop environment and everything, and it still had the weird character in PuTTY!

It turns out that it’s really simple — PuTTY defaults to an ISO-8859-1 character set and Ubuntu defaults to a UTF-8 character set. All I had to do was change my PuTTY settings to use UTF-8 instead:

(The highlight is kind of hard to see, but I clicked “Translation” underneath the “Window” category on the left side to get to that screen.)

After making that change in PuTTY, it works perfectly. Passing the gcc output to hexdump -C shows that the quote characters are represented as:

0xE2 0x80 0x98

and

0xE2 0x80 0x99

(which are UTF-8 sequences for ‘ and ’, respectively). Sure enough, 0xE2 in ISO-8859-1 is â, and 0x80, 0x98, and 0x99 are C1 control codes, which don’t actually display a character. So that’s the “why” behind this whole situation.

I know this probably seems like a simple thing to write a blog post about, but sometimes it’s kind of freaky when you do a minimal install of a Linux distribution and little glitches like this pop up because you forgot to install or configure a standard package that everyone tends to use. Even though that wasn’t the case here, I know others will run into the same problem and suspect the same thing I did originally, so I hope this helps someone else out (and maybe teaches a little bit of trivia in the process)!

This should also apply to other PowerPC Macs such as the G3, G4, iBook, and various iMac models (I think)…

I wanted to install Ubuntu 11.10 onto my Mac mini after replacing its hard drive. I found some excellent netboot install directions by Evan Martin, which I was able to follow (although I used the files from this directory for the netboot). However, I ran into a small problem when beginning the install–the netboot image for 11.10 does not include the parallel ATA driver for Macs (pata_macio.ko). It causes the installer to not detect any hard drives.

The Ubuntu FAQ I linked to above suggests installing 11.04 and then upgrading to 11.10. I didn’t feel like doing an upgrade install, so I decided to go another route. Here’s how I did it (starting at the point where I was told that no hard drives could be detected)…

  1. I manually downloaded the PowerPC kernel package.
  2. Next, I extracted lib/modules/3.0.0-12-powerpc/kernel/drivers/ata/pata_macio.ko by opening the .deb file with Archive Manager, and stuck it in my TFTP server directory.
  3. Almost there…I used tftp to grab it from my TFTP server and put it in /tmp on the Mac mini
    • Get to a console on the Mac mini by pressing Alt-F2. Remember, on an Apple keyboard, Alt is the option key and you may have to hold down the “fn” key to get F2 to be recognized as F2 instead of a brightness key.
  4. Finally, I inserted the module with insmod, switched back to the installer by pressing Alt-F1, and continued on with my install. I think I had to go back one step and try again, and then the hard drive was recognized.

By the way, it sounds like this will be fixed in 12.04. Yippee! Until then, this is another way of getting it done. I hope this helps someone else out there…