Re: [PATCH] add PCI ROMs to sysfs

From: Jon Smirl
Date: Sun Aug 29 2004 - 00:00:13 EST


On Sat, 28 Aug 2004 17:15:10 +0100, Matthew Wilcox <willy@xxxxxxxxxx>
wrote:
> BTW, what do you think to moving all the ROM code from pci-sysfs.c to
a
> new file rom.c? There's not much sense to the ROM accessing code
being
> mixed in with the sysfs code like this. After all, we might choose
to
> expose the ROM in some other way in the future.

After thinking about this for a while it's a good idea. New patch
splits core ROM support into a new file, rom.c.

Patch applies against both current Linus bk and mm now.

If the ROM attribute on a Radeon card is empty, modprobe in the new
radeonfb driver. The driver will correct a hardware bug and the ROM
will appear.

=====
Jon Smirl
jonsmirl@xxxxxxxxx




__________________________________
Do you Yahoo!?
New and Improved Yahoo! Mail - 100MB free storage!
http://promotions.yahoo.com/new_mail Exposes PCI ROMs via sysfs. Four new routines for drivers to use when
accessing ROMs: pci_map_rom, pci_map_rom_copy, pci_unmap_rom, pci_remove_rom.
Handles shadow ROMs for laptops that compress actual ROMs.

Signed-off-by: "Jon Smirl" <jonsmirl@xxxxxxxxx>
diff -Nru a/arch/i386/pci/fixup.c b/arch/i386/pci/fixup.c
--- a/arch/i386/pci/fixup.c Sun Aug 29 00:22:21 2004
+++ b/arch/i386/pci/fixup.c Sun Aug 29 00:22:21 2004
@@ -255,3 +255,41 @@
}
DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE2, pci_fixup_nforce2);

+/*
+ * Fixup to mark boot BIOS video selected by BIOS before it changes
+ *
+ * From information provided by "Jon Smirl" <jonsmirl@xxxxxxxxx>
+ *
+ * The standard boot ROM sequence for an x86 machine uses the BIOS
+ * to select an initial video card for boot display. This boot video
+ * card will have it's BIOS copied to C0000 in system RAM.
+ * IORESOURCE_ROM_SHADOW is used to associate the boot video
+ * card with this copy. On laptops this copy has to be used since
+ * the main ROM may be compressed or combined with another image.
+ * See pci_map_rom() for use of this flag. IORESOURCE_ROM_SHADOW
+ * is marked here since the boot video device will be the only enabled
+ * video device at this point.
+ *
+ */static void __devinit pci_fixup_video(struct pci_dev *pdev)
+{
+ struct pci_dev *bridge;
+ struct pci_bus *bus;
+ u16 l;
+
+ if ((pdev->class >> 8) != PCI_CLASS_DISPLAY_VGA)
+ return;
+
+ /* Is VGA routed to us? */
+ bus = pdev->bus;
+ while (bus) {
+ bridge = bus->self;
+ if (bridge) {
+ pci_read_config_word(bridge, PCI_BRIDGE_CONTROL, &l);
+ if (!(l & PCI_BRIDGE_CTL_VGA))
+ return;
+ }
+ bus = bus->parent;
+ }
+ pdev->resource[PCI_ROM_RESOURCE].flags |= IORESOURCE_ROM_SHADOW;
+}
+DECLARE_PCI_FIXUP_HEADER(PCI_ANY_ID, PCI_ANY_ID, pci_fixup_video);
diff -Nru a/drivers/pci/Makefile b/drivers/pci/Makefile
--- a/drivers/pci/Makefile Sun Aug 29 00:22:21 2004
+++ b/drivers/pci/Makefile Sun Aug 29 00:22:21 2004
@@ -3,7 +3,8 @@
#

obj-y += access.o bus.o probe.o remove.o pci.o quirks.o \
- names.o pci-driver.o search.o pci-sysfs.o
+ names.o pci-driver.o search.o pci-sysfs.o \
+ rom.o
obj-$(CONFIG_PROC_FS) += proc.o

ifndef CONFIG_SPARC64
diff -Nru a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c
--- a/drivers/pci/pci-sysfs.c Sun Aug 29 00:22:21 2004
+++ b/drivers/pci/pci-sysfs.c Sun Aug 29 00:22:21 2004
@@ -5,6 +5,8 @@
* (C) Copyright 2002-2004 IBM Corp.
* (C) Copyright 2003 Matthew Wilcox
* (C) Copyright 2003 Hewlett-Packard
+ * (C) Copyright 2004 Jon Smirl <jonsmirl@xxxxxxxxx>
+ * (C) Copyright 2004 Silicon Graphics, Inc. Jesse Barnes <jbarnes@xxxxxxx>
*
* File attributes for PCI devices
*
@@ -20,6 +22,8 @@

#include "pci.h"

+static int sysfs_initialized; /* = 0 */
+
/* show configuration fields */
#define pci_config_attr(field, format_string) \
static ssize_t \
@@ -164,6 +168,40 @@
return count;
}

+/**
+ * pci_read_rom - read a PCI ROM
+ * @kobj: kernel object handle
+ * @buf: where to put the data we read from the ROM
+ * @off: file offset
+ * @count: number of bytes to read
+ *
+ * Put @count bytes starting at @off into @buf from the ROM in the PCI
+ * device corresponding to @kobj.
+ */
+static ssize_t
+pci_read_rom(struct kobject *kobj, char *buf, loff_t off, size_t count)
+{
+ struct pci_dev *pdev = to_pci_dev(container_of(kobj, struct device, kobj));
+ unsigned char *rom;
+ size_t size;
+
+ rom = pci_map_rom(pdev, &size); /* size starts out as PCI window size */
+ if (!rom)
+ return 0;
+
+ if (off >= size)
+ count = 0;
+ else {
+ if (off + count > size)
+ count = size - off;
+
+ memcpy_fromio(buf, rom + off, count);
+ }
+ pci_unmap_rom(pdev, rom);
+
+ return count;
+}
+
static struct bin_attribute pci_config_attr = {
.attr = {
.name = "config",
@@ -186,13 +224,67 @@
.write = pci_write_config,
};

-void pci_create_sysfs_dev_files (struct pci_dev *pdev)
+int pci_create_sysfs_dev_files (struct pci_dev *pdev)
{
+ if (!sysfs_initialized)
+ return -EACCES;
+
if (pdev->cfg_size < 4096)
sysfs_create_bin_file(&pdev->dev.kobj, &pci_config_attr);
else
sysfs_create_bin_file(&pdev->dev.kobj, &pcie_config_attr);

+ /* If the device has a ROM, try to expose it in sysfs. */
+ if (pci_resource_len(pdev, PCI_ROM_RESOURCE)) {
+ struct bin_attribute *rom_attr;
+
+ rom_attr = kmalloc(sizeof(*rom_attr), GFP_ATOMIC);
+ if (rom_attr) {
+ pdev->rom_attr = rom_attr;
+ rom_attr->size = pci_resource_len(pdev, PCI_ROM_RESOURCE);
+ rom_attr->attr.name = "rom";
+ rom_attr->attr.mode = S_IRUSR;
+ rom_attr->attr.owner = THIS_MODULE;
+ rom_attr->read = pci_read_rom;
+ sysfs_create_bin_file(&pdev->dev.kobj, rom_attr);
+ }
+ }
/* add platform-specific attributes */
pcibios_add_platform_entries(pdev);
+
+ return 0;
+}
+
+/**
+ * pci_remove_sysfs_dev_files - cleanup PCI specific sysfs files
+ * @pdev: device whose entries we should free
+ *
+ * Cleanup when @pdev is removed from sysfs.
+ */
+void pci_remove_sysfs_dev_files(struct pci_dev *pdev)
+{
+ if (pdev->cfg_size < 4096)
+ sysfs_remove_bin_file(&pdev->dev.kobj, &pci_config_attr);
+ else
+ sysfs_remove_bin_file(&pdev->dev.kobj, &pcie_config_attr);
+
+ if (pci_resource_len(pdev, PCI_ROM_RESOURCE)) {
+ if (pdev->rom_attr) {
+ sysfs_remove_bin_file(&pdev->dev.kobj, pdev->rom_attr);
+ kfree(pdev->rom_attr);
+ }
+ }
+}
+
+static int __init pci_sysfs_init(void)
+{
+ struct pci_dev *pdev = NULL;
+
+ sysfs_initialized = 1;
+ while ((pdev = pci_find_device(PCI_ANY_ID, PCI_ANY_ID, pdev)) != NULL)
+ pci_create_sysfs_dev_files(pdev);
+
+ return 0;
}
+
+__initcall(pci_sysfs_init);
diff -Nru a/drivers/pci/pci.h b/drivers/pci/pci.h
--- a/drivers/pci/pci.h Sun Aug 29 00:22:21 2004
+++ b/drivers/pci/pci.h Sun Aug 29 00:22:21 2004
@@ -2,7 +2,9 @@

extern int pci_hotplug (struct device *dev, char **envp, int num_envp,
char *buffer, int buffer_size);
-extern void pci_create_sysfs_dev_files(struct pci_dev *pdev);
+extern int pci_create_sysfs_dev_files(struct pci_dev *pdev);
+extern void pci_remove_sysfs_dev_files(struct pci_dev *pdev);
+extern void pci_cleanup_rom(struct pci_dev *dev);
extern int pci_bus_alloc_resource(struct pci_bus *bus, struct resource *res,
unsigned long size, unsigned long align,
unsigned long min, unsigned int type_mask,
diff -Nru a/drivers/pci/probe.c b/drivers/pci/probe.c
--- a/drivers/pci/probe.c Sun Aug 29 00:22:21 2004
+++ b/drivers/pci/probe.c Sun Aug 29 00:22:21 2004
@@ -170,7 +170,7 @@
if (sz && sz != 0xffffffff) {
sz = pci_size(l, sz, PCI_ROM_ADDRESS_MASK);
if (sz) {
- res->flags = (l & PCI_ROM_ADDRESS_ENABLE) |
+ res->flags = (l & IORESOURCE_ROM_ENABLE) |
IORESOURCE_MEM | IORESOURCE_PREFETCH |
IORESOURCE_READONLY | IORESOURCE_CACHEABLE;
res->start = l & PCI_ROM_ADDRESS_MASK;
diff -Nru a/drivers/pci/remove.c b/drivers/pci/remove.c
--- a/drivers/pci/remove.c Sun Aug 29 00:22:21 2004
+++ b/drivers/pci/remove.c Sun Aug 29 00:22:21 2004
@@ -16,6 +16,7 @@

msi_remove_pci_irq_vectors(dev);

+ pci_cleanup_rom(dev);
for (i = 0; i < PCI_NUM_RESOURCES; i++) {
struct resource *res = dev->resource + i;
if (res->parent)
@@ -26,6 +27,7 @@
static void pci_destroy_dev(struct pci_dev *dev)
{
pci_proc_detach_device(dev);
+ pci_remove_sysfs_dev_files(dev);
device_unregister(&dev->dev);

/* Remove the device from the device lists, and prevent any further
diff -Nru a/drivers/pci/rom.c b/drivers/pci/rom.c
--- /dev/null Wed Dec 31 16:00:00 1969
+++ b/drivers/pci/rom.c Sun Aug 29 00:22:21 2004
@@ -0,0 +1,226 @@
+/*
+ * drivers/pci/rom.c
+ *
+ * (C) Copyright 2004 Jon Smirl <jonsmirl@xxxxxxxxx>
+ * (C) Copyright 2004 Silicon Graphics, Inc. Jesse Barnes <jbarnes@xxxxxxx>
+ *
+ * PCI ROM access routines
+ *
+ */
+
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/pci.h>
+
+#include "pci.h"
+
+/**
+ * pci_enable_rom - enable ROM decoding for a PCI device
+ * @dev: PCI device to enable
+ *
+ * Enable ROM decoding on @dev. This involves simply turning on the last
+ * bit of the PCI ROM BAR. Note that some cards may share address decoders
+ * between the ROM and other resources, so enabling it may disable access
+ * to MMIO registers or other card memory.
+ */
+static void
+pci_enable_rom(struct pci_dev *pdev)
+{
+ u32 rom_addr;
+
+ pci_read_config_dword(pdev, pdev->rom_base_reg, &rom_addr);
+ rom_addr |= PCI_ROM_ADDRESS_ENABLE;
+ pci_write_config_dword(pdev, pdev->rom_base_reg, rom_addr);
+}
+
+/**
+ * pci_disable_rom - disable ROM decoding for a PCI device
+ * @dev: PCI device to disable
+ *
+ * Disable ROM decoding on a PCI device by turning off the last bit in the
+ * ROM BAR.
+ */
+static void
+pci_disable_rom(struct pci_dev *pdev)
+{
+ u32 rom_addr;
+ pci_read_config_dword(pdev, pdev->rom_base_reg, &rom_addr);
+ rom_addr &= ~PCI_ROM_ADDRESS_ENABLE;
+ pci_write_config_dword(pdev, pdev->rom_base_reg, rom_addr);
+}
+
+/**
+ * pci_map_rom - map a PCI ROM to kernel space
+ * @dev: pointer to pci device struct
+ * @size: pointer to receive size of pci window over ROM
+ * @return: kernel virtual pointer to image of ROM
+ *
+ * Map a PCI ROM into kernel space. If ROM is boot video ROM,
+ * the shadow BIOS copy will be returned instead of the
+ * actual ROM.
+ */
+unsigned char *
+pci_map_rom(struct pci_dev *pdev, size_t *size)
+{
+ struct resource *res = &pdev->resource[PCI_ROM_RESOURCE];
+ loff_t start;
+ unsigned char *rom, *image;
+ int last_image;
+
+ if (res->flags & IORESOURCE_ROM_SHADOW) { /* IORESOURCE_ROM_SHADOW only set on x86 */
+ start = (loff_t)0xC0000; /* primary video rom always starts here */
+ *size = 0x20000; /* cover C000:0 through E000:0 */
+ } else {
+ if (res->flags & IORESOURCE_ROM_COPY) {
+ *size = pci_resource_len(pdev, PCI_ROM_RESOURCE);
+ return (unsigned char *)pci_resource_start(pdev, PCI_ROM_RESOURCE);
+ } else {
+ /* assign the ROM an address if it doesn't have one */
+ if (res->parent == NULL)
+ pci_assign_resource(pdev, PCI_ROM_RESOURCE);
+
+ start = pci_resource_start(pdev, PCI_ROM_RESOURCE);
+ *size = pci_resource_len(pdev, PCI_ROM_RESOURCE);
+ if (*size == 0)
+ return NULL;
+
+ /* Enable ROM space decodes */
+ pci_enable_rom(pdev);
+ }
+ }
+
+ rom = ioremap(start, *size);
+ if (!rom) {
+ /* restore enable if ioremap fails */
+ if (!(res->flags & (IORESOURCE_ROM_ENABLE | IORESOURCE_ROM_SHADOW | IORESOURCE_ROM_COPY)))
+ pci_disable_rom(pdev);
+ return NULL;
+ }
+
+ /* Try to find the true size of the ROM since sometimes the PCI window */
+ /* size is much larger than the actual size of the ROM. */
+ /* True size is important if the ROM is going to be copied. */
+ image = rom;
+ do {
+ char *pds;
+ /* Standard PCI ROMs start out with these bytes 55 AA */
+ if (readb(image) != 0x55)
+ break;
+ if (readb(image + 1) != 0xAA)
+ break;
+ /* get the PCI data structure and check its signature */
+ pds = image + readw(image + 24);
+ if (readb(pds) != 'P')
+ break;
+ if (readb(pds + 1) != 'C')
+ break;
+ if (readb(pds + 2) != 'I')
+ break;
+ if (readb(pds + 3) != 'R')
+ break;
+ last_image = readb(pds + 21) & 0x80;
+ /* this length is reliable */
+ image += readw(pds + 16) * 512;
+ } while (!last_image);
+
+ *size = image - rom;
+
+ return rom;
+}
+
+/**
+ * pci_map_rom_copy - map a PCI ROM to kernel space, create a copy
+ * @dev: pointer to pci device struct
+ * @size: pointer to receive size of pci window over ROM
+ * @return: kernel virtual pointer to image of ROM
+ *
+ * Map a PCI ROM into kernel space. If ROM is boot video ROM,
+ * the shadow BIOS copy will be returned instead of the
+ * actual ROM.
+ */
+unsigned char *
+pci_map_rom_copy(struct pci_dev *pdev, size_t *size)
+{
+ struct resource *res = &pdev->resource[PCI_ROM_RESOURCE];
+ unsigned char *rom;
+
+ rom = pci_map_rom(pdev, size);
+ if (!rom)
+ return NULL;
+
+ if (res->flags & (IORESOURCE_ROM_COPY | IORESOURCE_ROM_SHADOW))
+ return rom;
+
+ res->start = (unsigned long)kmalloc(*size, GFP_KERNEL);
+ if (!res->start)
+ return rom;
+
+ res->end = res->start + *size;
+ memcpy((void*)res->start, rom, *size);
+ pci_unmap_rom(pdev, rom);
+ res->flags |= IORESOURCE_ROM_COPY;
+
+ return (unsigned char *)res->start;
+}
+
+/**
+ * pci_unmap_rom - unmap the ROM from kernel space
+ * @dev: pointer to pci device struct
+ * @rom: virtual address of the previous mapping
+ *
+ * Remove a mapping of a previously mapped ROM
+ */
+void
+pci_unmap_rom(struct pci_dev *pdev, unsigned char *rom)
+{
+ struct resource *res = &pdev->resource[PCI_ROM_RESOURCE];
+
+ if (res->flags & IORESOURCE_ROM_COPY)
+ return;
+
+ iounmap(rom);
+
+ /* Disable again before continuing, leave enabled if pci=rom */
+ if (!(res->flags & (IORESOURCE_ROM_ENABLE | IORESOURCE_ROM_SHADOW)))
+ pci_disable_rom(pdev);
+}
+
+/**
+ * pci_remove_rom - disable the ROM and remove its sysfs attribute
+ * @dev: pointer to pci device struct
+ *
+ */
+void
+pci_remove_rom(struct pci_dev *pdev)
+{
+ struct resource *res = &pdev->resource[PCI_ROM_RESOURCE];
+
+ if (pci_resource_len(pdev, PCI_ROM_RESOURCE))
+ sysfs_remove_bin_file(&pdev->dev.kobj, pdev->rom_attr);
+ if (!(res->flags & (IORESOURCE_ROM_ENABLE | IORESOURCE_ROM_SHADOW | IORESOURCE_ROM_COPY)))
+ pci_disable_rom(pdev);
+}
+
+/**
+ * pci_cleanup_rom - internal routine for freeing the ROM copy created
+ * by pci_map_rom_copy called from remove.c
+ * @dev: pointer to pci device struct
+ *
+ */
+void
+pci_cleanup_rom(struct pci_dev *pdev)
+{
+ struct resource *res = &pdev->resource[PCI_ROM_RESOURCE];
+ if (res->flags & IORESOURCE_ROM_COPY) {
+ kfree((void*)res->start);
+ res->flags &= ~IORESOURCE_ROM_COPY;
+ res->start = 0;
+ res->end = 0;
+ }
+}
+
+EXPORT_SYMBOL(pci_map_rom);
+EXPORT_SYMBOL(pci_map_rom_copy);
+EXPORT_SYMBOL(pci_unmap_rom);
+EXPORT_SYMBOL(pci_remove_rom);
diff -Nru a/drivers/pci/setup-res.c b/drivers/pci/setup-res.c
--- a/drivers/pci/setup-res.c Sun Aug 29 00:22:21 2004
+++ b/drivers/pci/setup-res.c Sun Aug 29 00:22:21 2004
@@ -56,7 +56,7 @@
if (resno < 6) {
reg = PCI_BASE_ADDRESS_0 + 4 * resno;
} else if (resno == PCI_ROM_RESOURCE) {
- new |= res->flags & PCI_ROM_ADDRESS_ENABLE;
+ new |= res->flags & IORESOURCE_ROM_ENABLE;
reg = dev->rom_base_reg;
} else {
/* Hmm, non-standard resource. */
diff -Nru a/include/linux/ioport.h b/include/linux/ioport.h
--- a/include/linux/ioport.h Sun Aug 29 00:22:21 2004
+++ b/include/linux/ioport.h Sun Aug 29 00:22:21 2004
@@ -82,6 +82,11 @@
#define IORESOURCE_MEM_SHADOWABLE (1<<5) /* dup: IORESOURCE_SHADOWABLE */
#define IORESOURCE_MEM_EXPANSIONROM (1<<6)

+/* PCI ROM control bits (IORESOURCE_BITS) */
+#define IORESOURCE_ROM_ENABLE (1<<0) /* ROM is enabled, same as PCI_ROM_ADDRESS_ENABLE */
+#define IORESOURCE_ROM_SHADOW (1<<1) /* ROM is copy at C000:0 */
+#define IORESOURCE_ROM_COPY (1<<2) /* ROM is alloc'd copy, resource field overlaid */
+
/* PC/ISA/whatever - the normal PC address spaces: IO and memory */
extern struct resource ioport_resource;
extern struct resource iomem_resource;
diff -Nru a/include/linux/pci.h b/include/linux/pci.h
--- a/include/linux/pci.h Sun Aug 29 00:22:21 2004
+++ b/include/linux/pci.h Sun Aug 29 00:22:21 2004
@@ -537,6 +537,7 @@
unsigned int is_busmaster:1; /* device is busmaster */

u32 saved_config_space[16]; /* config space saved at suspend time */
+ struct bin_attribute *rom_attr; /* attribute descriptor for sysfs ROM entry */
#ifdef CONFIG_PCI_NAMES
#define PCI_NAME_SIZE 96
#define PCI_NAME_HALF __stringify(43) /* less than half to handle slop */
@@ -777,6 +778,12 @@
int pci_dac_set_dma_mask(struct pci_dev *dev, u64 mask);
int pci_set_consistent_dma_mask(struct pci_dev *dev, u64 mask);
int pci_assign_resource(struct pci_dev *dev, int i);
+
+/* ROM control related routines */
+unsigned char *pci_map_rom(struct pci_dev *pdev, size_t *size);
+unsigned char *pci_map_rom_copy(struct pci_dev *pdev, size_t *size);
+void pci_unmap_rom(struct pci_dev *pdev, unsigned char *rom);
+void pci_remove_rom(struct pci_dev *pdev);

/* Power management related routines */
int pci_save_state(struct pci_dev *dev, u32 *buffer);