Re: VPD in sysfs
From: Matthew Wilcox
Date: Fri Aug 20 2004 - 09:24:25 EST
On Sat, Aug 14, 2004 at 07:29:32PM +0100, Matthew Wilcox wrote:
> Thoughts? Since there's at least four and probably more ways of getting
> at VPD, we either need to fill in some VPD structs at initialisation or
> have some kind of vpd_ops that a driver can fill in so the core can get
> at the data.
I've tried the first option -- creating a large block of sysfs entries for
all the VPD entries that are present. However, I've come upon a problem
with sysfs that prevents me from doing so.
Basically, the problem is that sysfs doesn't pass the attribute that's
being invoked to the attribute ->show method. So I can't determine
which one is being read. This isn't a problem for any other sysfs attribute
because they're all static, but for dynamically created attributes, it's
not possible to work this way.
What I wanted to have was:
/sys/devices/pci0000:00/0000:00:00.0/vpd/
/sys/devices/pci0000:00/0000:00:00.0/vpd/PN (contents "6181682A")
/sys/devices/pci0000:00/0000:00:00.0/vpd/EC (contents "4950262536")
/sys/devices/pci0000:00/0000:00:00.0/vpd/SN (contents "00000194")
/sys/devices/pci0000:00/0000:00:00.0/vpd/FN (contents "135722")
/sys/devices/pci0000:00/0000:00:00.0/vpd/MN (contents "1037")
...
(note, this example is taken from table 6-8 in PCI 2.1)
So I have code that iterates over each element in a block of raw VPD,
extracts each name and value and creates sysfs entries for them ... but
I can't write the show method. Here's the VPD file (compiles, haven't
hooked any code up to it yet):
/*
* drivers/base/vpd.c
*
* Expose Vital Product Data through sysfs
*
* Copyright (c) Matthew Wilcox 2004
*/
#include <linux/device.h>
/* This should really be in a common file shared with drivers/pnp/isapnp */
enum isapnp_tag {
ISA_TAG_COMPAT = 0x03,
ISA_TAG_VEND_S = 0x0e,
ISA_TAG_END = 0x0f,
ISA_TAG_IDSTR = 0x82,
ISA_TAG_VEND_L = 0x84,
ISA_TAG_VPD = 0x90,
};
static inline int vpd_get_len(char *data, int *idx, enum isapnp_tag *tag)
{
int i, x, len;
i = *idx;
x = data[i++];
if (x & 0x80) {
len = data[i] | (data[i + 1] << 8);
i += 2;
} else {
len = x & 7;
x >>= 3;
}
*idx = i;
*tag = x;
return len;
}
static int vpd_count_items(char *data)
{
int i = 0;
int count = 0;
for (;;) {
int j;
enum isapnp_tag tag;
int len = vpd_get_len(data, &i, &tag);
if (tag == ISA_TAG_END)
break;
if (tag != ISA_TAG_VPD)
continue;
j = i;
while (j < i + len) {
count++;
j += 2;
j += data[j] + 1;
}
}
return count;
}
static ssize_t vpd_show(struct device *dev, char *buf)
{
return 0;
}
static struct attribute * vpd_create_attr(unsigned char *data)
{
struct device_attribute *attr;
char *buf;
unsigned int len = data[2];
attr = kmalloc(sizeof(*attr) + 4 + len, GFP_KERNEL);
if (!attr)
return NULL;
buf = (char *)(attr + 1);
buf[0] = data[0];
buf[1] = data[1];
buf[2] = '\0';
memcpy(buf, data + 3, len);
attr->attr.name = buf;
attr->attr.mode = 0644;
attr->show = vpd_show;
attr->store = NULL;
return &attr->attr;
}
static int vpd_create_attrs(struct attribute **attrs, int count, unsigned char *data)
{
int i = 0;
int k = 0;
for (;;) {
int j;
enum isapnp_tag tag;
int len = vpd_get_len(data, &i, &tag);
if (tag == ISA_TAG_END)
break;
if (tag != ISA_TAG_VPD)
continue;
j = i;
while (j < i + len) {
attrs[k] = vpd_create_attr(data + j);
if (!attrs[k])
return -ENOMEM;
k++;
j += 2;
j += data[j] + 1;
}
}
BUG_ON(k != count);
attrs[count] = NULL;
return 0;
}
/**
* device_add_vpd - Add VPD files to a device
* @dev: The device that this VPD pertains to
* @vpd: A memory region containing VPD data
*/
int device_add_vpd(struct device *dev, unsigned char *data)
{
int err, i;
int count = vpd_count_items(data);
if (count < 1)
return count;
dev->vpd = kmalloc(sizeof(*dev->vpd) + (count + 1) * sizeof(void *),
GFP_KERNEL);
if (!dev->vpd)
return -ENOMEM;
memset(dev->vpd, 0, sizeof(*dev->vpd) + (count + 1) * sizeof(void *));
dev->vpd->name = "vpd";
dev->vpd->attrs = (struct attribute **)(dev->vpd + 1);
err = vpd_create_attrs(dev->vpd->attrs, count, data);
if (err)
goto fail;
err = sysfs_create_group(&dev->kobj, dev->vpd);
if (err)
goto fail;
return 0;
fail:
for (i = 0; i < count; i++) {
kfree(dev->vpd->attrs[i]);
}
kfree(dev->vpd);
return err;
}
void device_remove_vpd(struct device *dev)
{
int i = 0;
sysfs_remove_group(&dev->kobj, dev->vpd);
while (dev->vpd->attrs[i] != NULL) {
kfree(dev->vpd->attrs[i]);
i++;
}
kfree(dev->vpd->attrs);
kfree(dev->vpd);
dev->vpd = NULL;
}
EXPORT_SYMBOL(device_add_vpd);
EXPORT_SYMBOL(device_remove_vpd);
--
"Next the statesmen will invent cheap lies, putting the blame upon
the nation that is attacked, and every man will be glad of those
conscience-soothing falsities, and will diligently study them, and refuse
to examine any refutations of them; and thus he will by and by convince
himself that the war is just, and will thank God for the better sleep
he enjoys after this process of grotesque self-deception." -- Mark Twain
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/