blob: 05cb3a0428ff06336e7fc165b4cbcc7a95263c57 [file] [log] [blame]
/*
* (C) 2004-2005 Dominik Brodowski <linux@brodo.de>
*
* Partly based on cardctl.c from pcmcia-cs-3.2.7/cardmgr/, which states
* in its header:
*
* The initial developer of the original code is David A. Hinds
* <dahinds@users.sourceforge.net>. Portions created by David A. Hinds
* are Copyright (C) 1999 David A. Hinds. All Rights Reserved.
*
* Licensed under the terms of the GNU GPL License version 2.
*/
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <libgen.h>
#include <locale.h>
#include <ctype.h>
#include <sysfs/libsysfs.h>
#include <getopt.h>
#define MAX_SOCKET 8
static const char * const fn[] = {
"multifunction",
"memory",
"serial",
"parallel",
"fixed disk",
"video",
"network",
"AIMS",
"SCSI"
};
/* crc32hash.c - derived from linux/lib/crc32.c, GNU GPL v2 */
static unsigned int crc32(unsigned char const *p, unsigned int len)
{
int i;
unsigned int crc = 0;
while (len--) {
crc ^= *p++;
for (i = 0; i < 8; i++)
crc = (crc >> 1) ^ ((crc & 1) ? 0xedb88320 : 0);
}
return crc;
}
static int pccardctl_power_socket(unsigned long socket_no, unsigned int power)
{
int ret;
char file[SYSFS_PATH_MAX];
struct sysfs_attribute *attr;
snprintf(file, SYSFS_PATH_MAX,
"/sys/class/pcmcia_socket/pcmcia_socket%lu/card_pm_state",
socket_no);
attr = sysfs_open_attribute(file);
if (!attr)
return -ENODEV;
ret = sysfs_write_attribute(attr, power ? "off" : "on", power ? 3 : 2);
sysfs_close_attribute(attr);
return ret;
}
static int pccardctl_echo_one(unsigned long socket_no, const char *in_file)
{
int ret;
char file[SYSFS_PATH_MAX];
struct sysfs_attribute *attr;
snprintf(file, SYSFS_PATH_MAX,
"/sys/class/pcmcia_socket/pcmcia_socket%lu/%s",
socket_no, in_file);
attr = sysfs_open_attribute(file);
if (!attr)
return -ENODEV;
ret = sysfs_write_attribute(attr, "42", 2);
sysfs_close_attribute(attr);
return ret;
}
static int pccardctl_socket_exists(unsigned long socket_no)
{
char file[SYSFS_PATH_MAX];
snprintf(file, SYSFS_PATH_MAX,
"/sys/class/pcmcia_socket/pcmcia_socket%lu/card_insert",
socket_no);
return !(sysfs_path_is_file(file));
}
static int read_out_file(char *file, char **output)
{
struct sysfs_attribute *attr = sysfs_open_attribute(file);
int ret;
char *result = NULL;
*output = NULL;
if (!attr)
return -EIO;
ret = sysfs_read_attribute(attr);
if (ret || !attr->value || !attr->len || (attr->len > SYSFS_PATH_MAX))
goto close_out;
result = malloc(attr->len + 1);
if (result) {
memcpy(result, attr->value, attr->len);
result[attr->len] = '\0';
if (result[attr->len - 1] == '\n')
result[attr->len - 1] = '\0';
*output = result;
} else
ret = -ENOMEM;
close_out:
sysfs_close_attribute(attr);
return ret;
}
static int pccardctl_get_string_socket(unsigned long socket_no,
const char *in_file, char **output)
{
char file[SYSFS_PATH_MAX];
snprintf(file, SYSFS_PATH_MAX,
"/sys/class/pcmcia_socket/pcmcia_socket%lu/%s",
socket_no, in_file);
return read_out_file(file, output);
}
static int pccardctl_get_string(unsigned long socket_no,
const char *in_file, char **output)
{
char file[SYSFS_PATH_MAX];
snprintf(file, SYSFS_PATH_MAX, "/sys/bus/pcmcia/devices/%lu.0/%s",
socket_no, in_file);
return read_out_file(file, output);
}
static int pccardctl_get_one_f(unsigned long socket_no, unsigned int dev,
const char *in_file, unsigned int *result)
{
char *value;
char file[SYSFS_PATH_MAX];
int ret;
snprintf(file, SYSFS_PATH_MAX, "/sys/bus/pcmcia/devices/%lu.%u/%s",
socket_no, dev, in_file);
ret = read_out_file(file, &value);
if (ret || !value)
return -EINVAL;
if (sscanf(value, "0x%X", result) != 1)
return -EIO;
return 0;
}
static int pccardctl_get_one(unsigned long socket_no, const char *in_file,
unsigned int *result)
{
return pccardctl_get_one_f(socket_no, 0, in_file, result);
}
static int pccardctl_get_power_device(unsigned long socket_no,
unsigned int func)
{
char *value;
char file[SYSFS_PATH_MAX];
snprintf(file, SYSFS_PATH_MAX,
"/sys/bus/pcmcia/devices/%lu.%u/pm_state",
socket_no, func);
read_out_file(file, &value);
if (value) {
if (!strncmp(value, "off", 3))
return 1;
return 0;
}
return -ENODEV;
}
static int pccardctl_get_power_socket(unsigned long socket_no)
{
char *value;
char file[SYSFS_PATH_MAX];
snprintf(file, SYSFS_PATH_MAX,
"/sys/class/pcmcia_socket/pcmcia_socket%lu/card_pm_state",
socket_no);
read_out_file(file, &value);
if (value) {
if (!strncmp(value, "off", 3))
return 1;
return 0;
}
return -ENODEV;
}
static int pccardctl_ident(unsigned long socket_no)
{
char *prod_id[4];
int valid_prod_id = 0;
int i;
unsigned int manf_id, card_id;
if (!pccardctl_socket_exists(socket_no))
return -ENODEV;
for (i = 1; i <= 4; i++) {
char file[SYSFS_PATH_MAX];
snprintf(file, SYSFS_PATH_MAX, "prod_id%u", i);
pccardctl_get_string(socket_no, file, &prod_id[i-1]);
if (prod_id[i-1])
valid_prod_id++;
}
if (valid_prod_id) {
printf(" product info: ");
for (i = 0; i < 4; i++) {
printf("%s\"%s\"", (i > 0) ? ", " : "",
prod_id[i] ? prod_id[i] : "");
if (prod_id[i])
free(prod_id[i]);
}
printf("\n");
} else
printf(" no product info available\n");
if (!pccardctl_get_one(socket_no, "manf_id", &manf_id))
if (!pccardctl_get_one(socket_no, "card_id", &card_id))
printf(" manfid: 0x%04x, 0x%04x\n", manf_id, card_id);
if (!pccardctl_get_one(socket_no, "func_id", &manf_id)) {
const char *s = "unknown";
if (manf_id < sizeof(fn)/sizeof(*fn))
s = fn[manf_id];
printf(" function: %d (%s)\n", manf_id, s);
}
return 0;
}
static int pccardctl_info(unsigned long socket_no)
{
int i;
unsigned int manf_id, card_id, func_id;
char *prod_id;
if (!pccardctl_socket_exists(socket_no))
return -ENODEV;
for (i = 1; i <= 4; i++) {
char file[SYSFS_PATH_MAX];
snprintf(file, SYSFS_PATH_MAX, "prod_id%u", i);
pccardctl_get_string(socket_no, file, &prod_id);
printf("PRODID_%d=\"%s\"\n", i, (prod_id) ? prod_id : "");
free(prod_id);
prod_id = NULL;
}
printf("MANFID=%04x,%04x\n",
(!pccardctl_get_one(socket_no, "manf_id",
&manf_id)) ? manf_id : 0,
(!pccardctl_get_one(socket_no, "card_id",
&card_id)) ? card_id : 0);
printf("FUNCID=%d\n",
(!pccardctl_get_one(socket_no, "func_id",
&func_id)) ? func_id : 255);
return 0;
}
static int pccardctl_status(unsigned long socket_no)
{
char *card_type;
char *card_voltage;
int susp;
int is_cardbus = 0;
int i, ret;
if (!pccardctl_socket_exists(socket_no))
return -ENODEV;
pccardctl_get_string_socket(socket_no, "card_type", &card_type);
if (!card_type) {
printf(" no card\n");
return 0;
}
pccardctl_get_string_socket(socket_no, "card_voltage", &card_voltage);
strncmp(card_type, "16", 2) ? is_cardbus = 0 : 1;
printf(" %s %s %s", card_voltage, card_type, is_cardbus ?
"CardBus card" : "PC Card");
susp = pccardctl_get_power_socket(socket_no);
if (susp > 0)
printf(" [suspended]");
printf("\n");
if (is_cardbus)
return 0;
for (i = 0; i < 4; i++) {
int function;
char drv[SYSFS_PATH_MAX];
char file[SYSFS_PATH_MAX];
if (pccardctl_get_one_f(socket_no, i, "function", &function))
continue;
printf(" Subdevice %u (function %u)", i, function);
snprintf(file, SYSFS_PATH_MAX,
"/sys/bus/pcmcia/devices/%lu.%u/driver",
socket_no, i);
ret = readlink(file, drv, sizeof(drv) - 1);
if (ret <= 0)
printf(" [unbound]");
else if (ret > 0) {
drv[ret] = '\0';
printf(" bound to driver \"%s\"", basename(drv));
}
susp = pccardctl_get_power_device(socket_no, i);
if (susp > 0)
printf(" [suspended]");
printf("\n");
}
return 0;
}
static void print_header(void)
{
printf("pcmciautils %s\n", PCMCIAUTILS_VERSION);
printf("Copyright (C) 2004-2005 Dominik Brodowski, "
"(C) 1999 David A. Hinds\n");
printf("Report errors and bugs to <linux-pcmcia@lists.infradead.org>,"
"please.\n");
}
static char *cmdname[] = {
"ls", /* needs to be first */
"insert",
"eject",
"suspend",
"resume",
"reset",
"info",
"status",
"config",
"ident",
};
static void print_help(void)
{
unsigned int i;
printf("Usage: pccardctl COMMAND\n");
printf("Supported commands are:\n");
for (i = 0; i < sizeof(cmdname)/sizeof(cmdname[0]); i++)
printf("\t%s\n", cmdname[i]);
}
static void print_unknown_arg(void)
{
print_header();
printf("invalid or unknown argument\n");
print_help();
}
static struct option pccardctl_opts[] = {
{ .name = "version", .has_arg = no_argument, .flag = NULL, .val = 'V'},
{ .name = "help", .has_arg = no_argument, .flag = NULL, .val = 'h'},
{ .name = "verbose", .has_arg = no_argument, .flag = NULL, .val = 'v'},
{ 0, 0, 0, 0 }
};
static void lspcmcia_socket_available_resources(unsigned long socket_no,
char *which)
{
char file[SYSFS_PATH_MAX];
struct sysfs_attribute *attr;
int ret, length, first = 0;
char *sep;
char *result = NULL;
snprintf(file, SYSFS_PATH_MAX,
"/sys/class/pcmcia_socket/pcmcia_socket%lu/available_resources_%s",
socket_no, which);
attr = sysfs_open_attribute(file);
if (!attr)
return;
ret = sysfs_read_attribute(attr);
if (ret)
goto close_out;
printf("\t\t\tAvailable %s:\t", which[0] == 'i' ? "ioports" : "iomem");
if (!attr->value || !attr->len || (attr->len < 5))
goto close_out;
result = malloc(attr->len + 1);
if (result) {
memcpy(result, attr->value, attr->len);
result[attr->len] = '\0';
if (result[attr->len - 1] == '\n')
result[attr->len - 1] = '\0';
} else
goto close_out;
ret = 0;
do {
sep = strchr(&result[ret], '\n');
if (sep) {
length = sep - &result[ret];
if (length > SYSFS_PATH_MAX)
break;
memcpy(file, &result[ret], length);
file[length] = '\0';
printf("%s\n\t\t\t\t\t\t", file);
first++;
ret += length + 1;
}
} while (sep);
if (result) {
printf("%s\n", &result[ret]);
first++;
}
close_out:
if (!first)
printf("--none--\n");
sysfs_close_attribute(attr);
return;
}
static void lspcmcia_socket(unsigned long socket_no, int verbose, char *driver)
{
char *card_voltage, *card_vpp, *card_vcc, *ready;
int pm_state;
pm_state = pccardctl_get_power_socket(socket_no);
pccardctl_get_string_socket(socket_no,
"available_resources_setup_done", &ready);
printf("\tConfiguration:\tstate: %s\tready: %s\n",
pm_state ? "suspended" : "on", ready ? ready : "unknown");
pccardctl_get_string_socket(socket_no, "card_voltage", &card_voltage);
pccardctl_get_string_socket(socket_no, "card_vpp", &card_vpp);
pccardctl_get_string_socket(socket_no, "card_vcc", &card_vcc);
if (card_voltage && card_vpp && card_vcc)
printf("\t\t\tVoltage: %s Vcc: %s Vpp: %s\n",
card_voltage, card_vcc, card_vpp);
if (verbose > 1) {
char *irq_mask_s;
int i, irqs = 0;
unsigned int irq_mask;
pccardctl_get_string_socket(socket_no, "card_irq_mask",
&irq_mask_s);
if (irq_mask_s && sscanf(irq_mask_s, "0x%X", &irq_mask) == 1) {
printf("\t\t\tAvailable IRQs: ");
for (i = 0; i < 32; i++) {
if (!(irq_mask & (1 << i)))
continue;
if (irqs)
printf(", ");
printf("%d", i);
irqs++;
}
if (!irqs)
printf("none");
printf("\n");
}
lspcmcia_socket_available_resources(socket_no, "io");
lspcmcia_socket_available_resources(socket_no, "mem");
}
return;
}
static void lspcmcia_device_resources(unsigned long socket_no, int fun)
{
char file[SYSFS_PATH_MAX];
struct sysfs_attribute *attr;
int ret, length;
char *sep;
char *result = NULL;
snprintf(file, SYSFS_PATH_MAX,
"/sys/bus/pcmcia/devices/%lu.%u/resources", socket_no, fun);
attr = sysfs_open_attribute(file);
if (!attr)
return;
ret = sysfs_read_attribute(attr);
if (ret)
goto close_out;
if (!attr->value || !attr->len || (attr->len < 5))
goto close_out;
result = malloc(attr->len + 1);
if (result) {
memcpy(result, attr->value, attr->len);
result[attr->len] = '\0';
if (result[attr->len - 1] == '\n')
result[attr->len - 1] = '\0';
} else
goto close_out;
ret = 0;
printf("\t\t\t");
do {
sep = strchr(&result[ret], '\n');
if (sep) {
length = sep - &result[ret];
if (length > SYSFS_PATH_MAX)
break;
memcpy(file, &result[ret], length);
file[length] = '\0';
printf("%s\n\t\t\t", file);
ret += length + 1;
}
} while (sep);
if (result)
printf("%s\n", &result[ret]);
close_out:
sysfs_close_attribute(attr);
return;
}
static int lspcmcia(unsigned long socket_no, int verbose)
{
char file[SYSFS_PATH_MAX];
char drv_s[SYSFS_PATH_MAX];
char dev_s[SYSFS_PATH_MAX];
char *drv;
char *dev;
char *res;
int ret, i;
if (!pccardctl_socket_exists(socket_no))
return -ENODEV;
snprintf(file, SYSFS_PATH_MAX,
"/sys/class/pcmcia_socket/pcmcia_socket%lu/device", socket_no);
ret = readlink(file, dev_s, sizeof(dev_s) - 1);
if (ret > 0) {
dev_s[ret] = '\0';
dev = basename(dev_s);
} else {
snprintf(file, SYSFS_PATH_MAX,
"/sys/class/pcmcia_socket/pcmcia_socket%lu", socket_no);
ret = readlink(file, dev_s, sizeof(dev_s) - 1);
if (ret <= 0)
return -ENODEV;
dev_s[ret] = '\0';
dev = basename(dirname(dev_s));
}
snprintf(file, SYSFS_PATH_MAX,
"/sys/class/pcmcia_socket/pcmcia_socket%lu/device/driver",
socket_no);
ret = readlink(file, drv_s, sizeof(drv_s) - 1);
if (ret <= 0) {
snprintf(file, SYSFS_PATH_MAX,
"/sys/class/pcmcia_socket/pcmcia_socket%lu/../driver",
socket_no);
ret = readlink(file, drv_s, sizeof(drv_s) - 1);
if (ret <= 0)
return -ENODEV;
}
drv_s[ret] = '\0';
drv = basename(drv_s);
printf("Socket %lu Bridge: \t[%s] \t(bus ID: %s)\n",
socket_no, drv, dev);
if (verbose)
lspcmcia_socket(socket_no, verbose, drv);
pccardctl_get_string_socket(socket_no, "card_type", &res);
if (!res)
return 0;
if (!strncmp(res, "32", 2)) {
printf(" CardBus card -- see \"lspci\" "
"for more information\n");
return 0;
}
for (i = 0; i < 4; i++) {
int function;
if (pccardctl_get_one_f(socket_no, i, "function", &function))
continue;
printf("Socket %lu Device %d:\t", socket_no, i);
snprintf(file, SYSFS_PATH_MAX,
"/sys/bus/pcmcia/devices/%lu.%u/driver",
socket_no, i);
ret = readlink(file, drv_s, sizeof(drv_s) - 1);
if (ret <= 0)
printf("[-- no driver --]\t");
else if (ret > 0) {
drv_s[ret] = '\0';
printf("[%s]\t\t", basename(drv_s));
}
printf("(bus ID: %lu.%d)\n", socket_no, i);
if (verbose) {
int j;
unsigned int manf_id, card_id;
int pm_state = pccardctl_get_power_device(socket_no, i);
printf("\tConfiguration:\tstate: %s\n",
pm_state ? "suspended" : "on");
lspcmcia_device_resources(socket_no, i);
printf("\tProduct Name: ");
for (j = 1; j <= 4; j++) {
snprintf(file, SYSFS_PATH_MAX, "prod_id%d", j);
pccardctl_get_string(socket_no, file, &res);
if (res)
printf("%s ", res);
}
printf("\n");
printf("\tIdentification:\t");
if (!pccardctl_get_one(socket_no, "manf_id", &manf_id))
if (!pccardctl_get_one(socket_no, "card_id",
&card_id))
printf("manf_id: 0x%04x\t"
"card_id: 0x%04x\n\t\t\t",
manf_id, card_id);
if (!pccardctl_get_one(socket_no, "func_id",
&manf_id)) {
const char *s = "unknown";
if (manf_id < sizeof(fn)/sizeof(*fn))
s = fn[manf_id];
printf("function: %d (%s)\n\t\t\t", manf_id, s);
}
for (j = 1; j <= 4; j++) {
snprintf(file, SYSFS_PATH_MAX, "prod_id%d", j);
pccardctl_get_string(socket_no, file, &res);
if (res)
printf("prod_id(%u): \"%s\" "
"(0x%08x)\n", j, res,
crc32(res, strlen(res)));
else
printf("prod_id(%u): --- (---)\n", j);
if (j < 4)
printf("\t\t\t");
}
}
}
return 0;
}
enum {
PCCARDCTL_LSPCMCIA, /* needs to be first */
PCCARDCTL_INSERT,
PCCARDCTL_EJECT,
PCCARDCTL_SUSPEND,
PCCARDCTL_RESUME,
PCCARDCTL_RESET,
PCCARDCTL_INFO,
PCCARDCTL_STATUS,
PCCARDCTL_CONFIG,
PCCARDCTL_IDENT,
NCMD
};
int main(int argc, char **argv)
{
extern char *optarg;
extern int optind, opterr, optopt;
int ret = 0;
int verbose = 0;
unsigned int cont = 1;
unsigned long socket = 0;
unsigned int socket_is_set = 0;
char *s;
unsigned int cmd;
do {
ret = getopt_long(argc, argv, "Vhvc:f:s:",
pccardctl_opts, NULL);
switch (ret) {
case -1:
cont = 0;
break;
case 'V':
print_header();
return 0;
case 'v':
verbose++;
break;
case 'h':
print_header();
print_help();
return 0;
case 'c':
case 's':
case 'f':
/* ignored */
fprintf(stderr, "ignoring parameter '%c'\n", ret);
break;
default:
print_unknown_arg();
return -EINVAL;
}
} while (cont);
if (strcmp(basename(argv[0]), "lspcmcia") == 0) {
cmd = 0;
goto check_socket;
}
if ((argc == optind) || (argc > (optind + 2))) {
print_unknown_arg();
return -EINVAL;
}
/* determine command */
for (cmd = 0; cmd < NCMD; cmd++)
if (strcmp(argv[optind], cmdname[cmd]) == 0)
break;
if (cmd == NCMD) {
print_unknown_arg();
return -EINVAL;
}
check_socket:
if (argc == optind+2) {
socket_is_set = 1;
socket = strtol(argv[optind+1], &s, 0);
if ((*argv[optind+1] == '\0') || (*s != '\0') ||
(socket >= MAX_SOCKET)) {
print_unknown_arg();
return -EINVAL;
}
}
for (cont = 0; cont < MAX_SOCKET; cont++) {
if (socket_is_set && (socket != cont))
continue;
if (!socket_is_set && !pccardctl_socket_exists(cont))
continue;
if (!socket_is_set && (cmd > PCCARDCTL_INFO))
printf("Socket %d:\n", cont);
ret = 0;
switch (cmd) {
case PCCARDCTL_LSPCMCIA:
ret = lspcmcia(cont, verbose);
break;
case PCCARDCTL_INSERT:
ret = pccardctl_echo_one(cont, "card_insert");
break;
case PCCARDCTL_EJECT:
ret = pccardctl_echo_one(cont, "card_eject");
break;
case PCCARDCTL_INFO:
ret = pccardctl_info(cont);
break;
case PCCARDCTL_IDENT:
ret = pccardctl_ident(cont);
break;
case PCCARDCTL_SUSPEND:
ret = pccardctl_power_socket(cont, 1);
break;
case PCCARDCTL_RESET:
ret = pccardctl_power_socket(cont, 1);
if (ret && socket_is_set)
return ret;
/* fall through */
case PCCARDCTL_RESUME:
ret = pccardctl_power_socket(cont, 0);
break;
case PCCARDCTL_STATUS:
ret = pccardctl_status(cont);
break;
default:
fprintf(stderr,
"command '%s' not yet handled by pccardctl\n",
cmdname[cmd]);
return -EAGAIN;
}
if (ret && socket_is_set)
return ret;
}
return 0;
}