summaryrefslogtreecommitdiffstats
path: root/arch/i386/kernel/mca.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/i386/kernel/mca.c')
-rw-r--r--arch/i386/kernel/mca.c622
1 files changed, 622 insertions, 0 deletions
diff --git a/arch/i386/kernel/mca.c b/arch/i386/kernel/mca.c
new file mode 100644
index 000000000..1f8d0799d
--- /dev/null
+++ b/arch/i386/kernel/mca.c
@@ -0,0 +1,622 @@
+/*
+ * linux/arch/i386/kernel/mca.c
+ * Written by Martin Kolinek, February 1996
+ *
+ * Changes:
+ * July 28, 1996: fixed up integrated SCSI detection. Chris Beauregard
+ * August 3rd, 1996: made mca_info local, made integrated registers
+ * accessible through standard function calls, added name field,
+ * more sanity checking. Chris Beauregard
+ * August 9, 1996: Rewrote /proc/mca. cpbeaure
+ */
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/mca.h>
+#include <asm/system.h>
+#include <asm/io.h>
+#include <linux/proc_fs.h>
+#include <linux/mman.h>
+#include <linux/config.h>
+#include <linux/mm.h>
+#include <linux/pagemap.h>
+#include <linux/ioport.h>
+#include <asm/uaccess.h>
+#include <linux/init.h>
+
+/* This structure holds MCA information. Each (plug-in) adapter has
+ * eight POS registers. Then the machine may have integrated video and
+ * SCSI subsystems, which also have eight POS registers.
+ * Other miscellaneous information follows.
+*/
+struct MCA_adapter {
+ unsigned char pos[8]; /* POS registers */
+ char name[32]; /* name of the device - provided by driver */
+ char procname[8]; /* name of /proc/mca file */
+ MCA_ProcFn procfn; /* /proc info callback */
+ void* dev; /* device/context info for callback */
+};
+
+struct MCA_info {
+ /* one for each of the 8 possible slots, plus one for integrated SCSI
+ and one for integrated video. */
+ struct MCA_adapter slot[MCA_NUMADAPTERS];
+};
+
+/* The mca_info structure pointer. If MCA bus is present, the function
+ * mca_probe() is invoked. The function puts motherboard, then all
+ * adapters into setup mode, allocates and fills an MCA_info structure,
+ * and points this pointer to the structure. Otherwise the pointer
+ * is set to zero.
+*/
+static struct MCA_info* mca_info = 0;
+
+/*MCA registers*/
+#define MCA_MOTHERBOARD_SETUP_REG 0x94
+#define MCA_ADAPTER_SETUP_REG 0x96
+#define MCA_POS_REG(n) (0x100+(n))
+
+#define MCA_ENABLED 0x01 /* POS 2, set if adapter enabled */
+
+/*--------------------------------------------------------------------*/
+
+#ifdef CONFIG_PROC_FS
+static long mca_do_proc_init( long memory_start, long memory_end );
+static int mca_default_procfn( char* buf, int slot );
+
+static long proc_mca_read( struct inode*, struct file*, char* buf, unsigned long count );
+static struct file_operations proc_mca_operations = {
+ NULL, proc_mca_read,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
+};
+static struct inode_operations proc_mca_inode_operations = {
+ &proc_mca_operations,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
+};
+#endif
+
+/*--------------------------------------------------------------------*/
+
+__initfunc(long mca_init(long memory_start, long memory_end))
+{
+ unsigned int i, j;
+ int foundscsi = 0;
+
+ /* WARNING: Be careful when making changes here. Putting an adapter
+ * and the motherboard simultaneously into setup mode may result in
+ * damage to chips (according to The Indispensible PC Hardware Book
+ * by Hans-Peter Messmer). Also, we disable system interrupts (so
+ * that we are not disturbed in the middle of this).
+ */
+
+ /*
+ * Make sure the MCA bus is present
+ */
+
+ if (!MCA_bus)
+ return memory_start;
+ cli();
+
+ /*
+ * Allocate MCA_info structure (at address divisible by 8)
+ */
+
+ if( ((memory_start+7)&(~7)) > memory_end )
+ {
+ /* uh oh */
+ return memory_start;
+ }
+
+ mca_info = (struct MCA_info*) ((memory_start+7)&(~7));
+ memory_start = ((long)mca_info) + sizeof(struct MCA_info);
+
+ /*
+ * Make sure adapter setup is off
+ */
+
+ outb_p(0, MCA_ADAPTER_SETUP_REG);
+
+ /*
+ * Put motherboard into video setup mode, read integrated video
+ * pos registers, and turn motherboard setup off.
+ */
+
+ outb_p(0xdf, MCA_MOTHERBOARD_SETUP_REG);
+ mca_info->slot[MCA_INTEGVIDEO].name[0] = 0;
+ for (j=0; j<8; j++) {
+ mca_info->slot[MCA_INTEGVIDEO].pos[j] = inb_p(MCA_POS_REG(j));
+ }
+
+ /* Put motherboard into scsi setup mode, read integrated scsi
+ * pos registers, and turn motherboard setup off.
+ *
+ * It seems there are two possible SCSI registers. Martin says that
+ * for the 56,57, 0xf7 is the one, but fails on the 76.
+ * Alfredo (apena@vnet.ibm.com) says
+ * 0xfd works on his machine. We'll try both of them. I figure it's
+ * a good bet that only one could be valid at a time. This could
+ * screw up though if one is used for something else on the other
+ * machine.
+ */
+
+ outb_p(0xf7, MCA_MOTHERBOARD_SETUP_REG);
+ mca_info->slot[MCA_INTEGSCSI].name[0] = 0;
+ for (j=0; j<8; j++) {
+ if( (mca_info->slot[MCA_INTEGSCSI].pos[j] = inb_p(MCA_POS_REG(j))) != 0xff )
+ {
+ /* 0xff all across means no device. 0x00 means something's
+ broken, but a device is probably there. However, if you get
+ 0x00 from a motherboard register it won't matter what we
+ find. For the record, on the 57SLC, the integrated SCSI
+ adapter has 0xffff for the adapter ID, but nonzero for
+ other registers. */
+ foundscsi = 1;
+ }
+ }
+ if( !foundscsi )
+ {
+ /*
+ * Didn't find it at 0xfd, try somewhere else...
+ */
+ outb_p(0xfd, MCA_MOTHERBOARD_SETUP_REG);
+ for (j=0; j<8; j++)
+ mca_info->slot[MCA_INTEGSCSI].pos[j] = inb_p(MCA_POS_REG(j));
+ }
+
+ /* turn off motherboard setup */
+ outb_p(0xff, MCA_MOTHERBOARD_SETUP_REG);
+
+ /*
+ * Now loop over MCA slots: put each adapter into setup mode, and
+ * read its pos registers. Then put adapter setup off.
+ */
+
+ for (i=0; i<MCA_MAX_SLOT_NR; i++) {
+ outb_p(0x8|(i&0xf), MCA_ADAPTER_SETUP_REG);
+ for (j=0; j<8; j++) mca_info->slot[i].pos[j]=inb_p(MCA_POS_REG(j));
+ mca_info->slot[i].name[0] = 0;
+ }
+ outb_p(0, MCA_ADAPTER_SETUP_REG);
+
+ /*
+ * Enable interrupts and return memory start
+ */
+ sti();
+
+ request_region(0x60,0x01,"system control port B (MCA)");
+ request_region(0x90,0x01,"arbitration (MCA)");
+ request_region(0x91,0x01,"card Select Feedback (MCA)");
+ request_region(0x92,0x01,"system Control port A (MCA)");
+ request_region(0x94,0x01,"system board setup (MCA)");
+ request_region(0x96,0x02,"POS (MCA)");
+ request_region(0x100,0x08,"POS (MCA)");
+
+#ifdef CONFIG_PROC_FS
+ memory_start = mca_do_proc_init( memory_start, memory_end );
+#endif
+
+ return memory_start;
+}
+
+/*--------------------------------------------------------------------*/
+
+int mca_find_adapter( int id, int start )
+{
+ int slot_id = 0;
+ unsigned char status = 0;
+
+ if( mca_info == 0 || id == 0 || id == 0xffff ) {
+ return MCA_NOTFOUND;
+ }
+
+ for( ; start >= 0 && start < MCA_NUMADAPTERS; start += 1 ) {
+ slot_id = (mca_info->slot[start].pos[1] << 8)
+ + mca_info->slot[start].pos[0];
+ status = mca_info->slot[start].pos[2];
+
+ /* not sure about this. There's no point in returning
+ adapters that aren't enabled, since they can't actually
+ be used. However, they might be needed for statistical
+ purposes or something... */
+ if( !(status & MCA_ENABLED) ) {
+ continue;
+ }
+
+ if( id == slot_id ) {
+ return start;
+ }
+ }
+
+ return MCA_NOTFOUND;
+} /* mca_find_adapter() */
+
+/*--------------------------------------------------------------------*/
+
+unsigned char mca_read_stored_pos( int slot, int reg )
+{
+ if( slot < 0 || slot >= MCA_NUMADAPTERS || mca_info == 0 ) return 0;
+ if( reg < 0 || reg >= 8 ) return 0;
+ return mca_info->slot[slot].pos[reg];
+} /* mca_read_stored_pos() */
+
+/*--------------------------------------------------------------------*/
+
+unsigned char mca_read_pos( int slot, int reg )
+{
+ unsigned int byte = 0;
+
+ if( slot < 0 || slot >= MCA_MAX_SLOT_NR || mca_info == 0 ) return 0;
+ if( reg < 0 || reg >= 8 ) return 0;
+
+ cli();
+
+ /*make sure motherboard setup is off*/
+ outb_p(0xff, MCA_MOTHERBOARD_SETUP_REG);
+
+ /* read in the appropriate register */
+ outb_p(0x8|(slot&0xf), MCA_ADAPTER_SETUP_REG);
+ byte = inb_p(MCA_POS_REG(reg));
+ outb_p(0, MCA_ADAPTER_SETUP_REG);
+
+ sti();
+
+ /* make sure the stored values are consistent, while we're here */
+ mca_info->slot[slot].pos[reg] = byte;
+
+ return byte;
+} /* mca_read_pos() */
+
+/*--------------------------------------------------------------------*/
+/* Note that this a technically a Bad Thing, as IBM tech stuff says
+ you should only set POS values through their utilities.
+ However, some devices such as the 3c523 recommend that you write
+ back some data to make sure the configuration is consistent.
+ I'd say that IBM is right, but I like my drivers to work.
+ This function can't do checks to see if multiple devices end up
+ with the same resources, so you might see magic smoke if someone
+ screws up. */
+
+void mca_write_pos( int slot, int reg, unsigned char byte )
+{
+ if( slot < 0 || slot >= MCA_MAX_SLOT_NR ) return;
+ if( reg < 0 || reg >= 8 ) return;
+ if (mca_info == 0 ) return;
+
+ cli();
+
+ /*make sure motherboard setup is off*/
+ outb_p(0xff, MCA_MOTHERBOARD_SETUP_REG);
+
+ /* read in the appropriate register */
+ outb_p(0x8|(slot&0xf), MCA_ADAPTER_SETUP_REG);
+ outb_p( byte, MCA_POS_REG(reg) );
+ outb_p(0, MCA_ADAPTER_SETUP_REG);
+
+ sti();
+
+ /* update the global register list, while we have the byte */
+ mca_info->slot[slot].pos[reg] = byte;
+} /* mca_write_pos() */
+
+/*--------------------------------------------------------------------*/
+
+void mca_set_adapter_name( int slot, char* name )
+{
+ if( mca_info == 0 ) return;
+
+ if( slot >= 0 && slot < MCA_NUMADAPTERS ) {
+ strncpy( mca_info->slot[slot].name, name,
+ sizeof(mca_info->slot[slot].name) );
+ }
+}
+
+void mca_set_adapter_procfn( int slot, MCA_ProcFn procfn, void* dev)
+{
+ if( mca_info == 0 ) return;
+
+ if( slot >= 0 && slot < MCA_NUMADAPTERS ) {
+ mca_info->slot[slot].procfn = procfn;
+ mca_info->slot[slot].dev = dev;
+ }
+}
+
+char *mca_get_adapter_name( int slot )
+{
+ if( mca_info == 0 ) return 0;
+
+ if( slot >= 0 && slot < MCA_NUMADAPTERS ) {
+ return mca_info->slot[slot].name;
+ }
+
+ return 0;
+}
+
+int mca_isadapter( int slot )
+{
+ if( mca_info == 0 ) return 0;
+
+ if( slot >= MCA_MAX_SLOT_NR ) {
+ /* some integrated adapters have 0xffff for an ID, but
+ are still there. VGA, for example. */
+ int i;
+ for( i = 0; i < 8; i ++ ) {
+ if( mca_info->slot[slot].pos[i] != 0xff ) {
+ return 1;
+ }
+ }
+ return 0;
+ } else if( slot >= 0 && slot < MCA_NUMADAPTERS ) {
+ return (mca_info->slot[slot].pos[0] != 0xff ||
+ mca_info->slot[slot].pos[1] != 0xff);
+ }
+
+ return 0;
+}
+
+int mca_isenabled( int slot )
+{
+ if( mca_info == 0 ) return 0;
+
+ if( slot >= 0 && slot < MCA_NUMADAPTERS ) {
+ return (mca_info->slot[slot].pos[2] & MCA_ENABLED);
+ }
+
+ return 0;
+}
+
+/*--------------------------------------------------------------------*/
+
+#ifdef CONFIG_PROC_FS
+
+int get_mca_info(char *buf)
+{
+ int i, j, len = 0;
+
+ if( MCA_bus && mca_info != 0 )
+ {
+ /*
+ * Format pos registers of eight MCA slots
+ */
+ for (i=0; i<MCA_MAX_SLOT_NR; i++)
+ {
+ len += sprintf(buf+len, "Slot %d: ", i+1);
+ for (j=0; j<8; j++)
+ len += sprintf(buf+len, "%02x ", mca_info->slot[i].pos[j]);
+ len += sprintf( buf+len, " %s\n", mca_info->slot[i].name );
+ }
+
+ /*
+ * Format pos registers of integrated video subsystem
+ */
+
+ len += sprintf(buf+len, "Video: ");
+ for (j=0; j<8; j++)
+ len += sprintf(buf+len, "%02x ", mca_info->slot[MCA_INTEGVIDEO].pos[j]);
+ len += sprintf( buf+len, " %s\n", mca_info->slot[MCA_INTEGVIDEO].name );
+
+ /*
+ * Format pos registers of integrated SCSI subsystem
+ */
+
+ len += sprintf(buf+len, "SCSI: ");
+ for (j=0; j<8; j++)
+ len += sprintf(buf+len, "%02x ", mca_info->slot[MCA_INTEGSCSI].pos[j]);
+ len += sprintf( buf+len, " %s\n", mca_info->slot[MCA_INTEGSCSI].name );
+ }
+ else
+ {
+ /*
+ * Leave it empty if MCA not detected
+ * this should never happen
+ */
+ }
+
+ return len;
+}
+
+
+/*--------------------------------------------------------------------*/
+__initfunc(long mca_do_proc_init( long memory_start, long memory_end ))
+{
+ int i = 0;
+ struct proc_dir_entry* node = 0;
+
+ if( mca_info == 0 ) return memory_start; /* never happens */
+
+ proc_register( &proc_mca, &(struct proc_dir_entry) {
+ PROC_MCA_REGISTERS, 3, "pos", S_IFREG|S_IRUGO,
+ 1, 0, 0, 0, &proc_mca_inode_operations,} );
+
+ proc_register( &proc_mca, &(struct proc_dir_entry) {
+ PROC_MCA_MACHINE, 7, "machine", S_IFREG|S_IRUGO,
+ 1, 0, 0, 0, &proc_mca_inode_operations,} );
+
+ /* initialize /proc entries for existing adapters */
+ for( i = 0; i < MCA_NUMADAPTERS; i += 1 ) {
+ mca_info->slot[i].procfn = 0;
+ mca_info->slot[i].dev = 0;
+
+ if( ! mca_isadapter( i ) ) continue;
+ if( memory_start + sizeof(struct proc_dir_entry) > memory_end ) {
+ continue;
+ }
+ node = (struct proc_dir_entry*) memory_start;
+ memory_start += sizeof(struct proc_dir_entry);
+
+ if( i < MCA_MAX_SLOT_NR ) {
+ node->low_ino = PROC_MCA_SLOT + i;
+ node->namelen = sprintf( mca_info->slot[i].procname,
+ "slot%d", i+1 );
+ } else if( i == MCA_INTEGVIDEO ) {
+ node->low_ino = PROC_MCA_VIDEO;
+ node->namelen = sprintf( mca_info->slot[i].procname,
+ "video" );
+ } else if( i == MCA_INTEGSCSI ) {
+ node->low_ino = PROC_MCA_SCSI;
+ node->namelen = sprintf( mca_info->slot[i].procname,
+ "scsi" );
+ }
+ node->name = mca_info->slot[i].procname;
+ node->mode = S_IFREG | S_IRUGO;
+ node->ops = &proc_mca_inode_operations;
+ proc_register( &proc_mca, node );
+ }
+
+ return memory_start;
+} /* mca_do_proc_init() */
+
+/*--------------------------------------------------------------------*/
+
+int mca_default_procfn( char* buf, int slot )
+{
+ int len = 0, i;
+
+ /* this really shouldn't happen... */
+ if( mca_info == 0 ) {
+ *buf = 0;
+ return 0;
+ }
+
+ /* print out the basic information */
+
+ if( slot < MCA_MAX_SLOT_NR ) {
+ len += sprintf( buf+len, "Slot: %d\n", slot+1 );
+ } else if( slot == MCA_INTEGSCSI ) {
+ len += sprintf( buf+len, "Integrated SCSI Adapter\n" );
+ } else if( slot == MCA_INTEGVIDEO ) {
+ len += sprintf( buf+len, "Integrated Video Adapter\n" );
+ }
+ if( mca_info->slot[slot].name[0] ) {
+ /* drivers might register a name without /proc handler... */
+ len += sprintf( buf+len, "Adapter Name: %s\n",
+ mca_info->slot[slot].name );
+ } else {
+ len += sprintf( buf+len, "Adapter Name: Unknown\n" );
+ }
+ len += sprintf( buf+len, "Id: %02x%02x\n",
+ mca_info->slot[slot].pos[1], mca_info->slot[slot].pos[0] );
+ len += sprintf( buf+len, "Enabled: %s\nPOS: ",
+ mca_isenabled(slot) ? "Yes" : "No" );
+ for (i=0; i<8; i++) {
+ len += sprintf(buf+len, "%02x ", mca_info->slot[slot].pos[i]);
+ }
+ buf[len++] = '\n';
+ buf[len] = 0;
+
+ return len;
+} /* mca_default_procfn() */
+
+static int get_mca_machine_info( char* buf )
+{
+ int len = 0;
+
+ len += sprintf( buf+len, "Model Id: 0x%x\n", machine_id );
+ len += sprintf( buf+len, "Submodel Id: 0x%x\n", machine_submodel_id );
+ len += sprintf( buf+len, "BIOS Revision: 0x%x\n", BIOS_revision );
+
+ return len;
+}
+
+/*
+static int mca_not_implemented( char* buf )
+{
+ return sprintf( buf, "Sorry, not implemented yet...\n" );
+}
+*/
+
+static int mca_fill( char* page, int pid, int type, char** start,
+ off_t offset, int length)
+{
+ int len = 0;
+ int slot = 0;
+
+ switch( type ) {
+ case PROC_MCA_REGISTERS:
+ return get_mca_info( page );
+ case PROC_MCA_MACHINE:
+ return get_mca_machine_info( page );
+ case PROC_MCA_VIDEO:
+ slot = MCA_INTEGVIDEO;
+ break;
+ case PROC_MCA_SCSI:
+ slot = MCA_INTEGSCSI;
+ break;
+ default:
+ if( type < PROC_MCA_SLOT || type >= PROC_MCA_LAST ) {
+ return -EBADF;
+ }
+ slot = type - PROC_MCA_SLOT;
+ break;
+ }
+
+ /* if we made it here, we better have a valid slot */
+
+ /* get the standard info */
+ len = mca_default_procfn( page, slot );
+
+ /* do any device-specific processing, if there is any */
+ if( mca_info->slot[slot].procfn ) {
+ len += mca_info->slot[slot].procfn( page+len, slot,
+ mca_info->slot[slot].dev );
+ }
+
+ return len;
+} /* mca_fill() */
+
+/*
+ * Blatantly stolen from fs/proc/array.c, and thus is probably overkill
+ */
+
+#define PROC_BLOCK_SIZE (3*1024)
+
+long proc_mca_read( struct inode* inode, struct file* file,
+ char* buf, unsigned long count)
+{
+ unsigned long page;
+ char *start;
+ int length;
+ int end;
+ unsigned int type, pid;
+ struct proc_dir_entry *dp;
+
+ if (count < 0)
+ return -EINVAL;
+ if (count > PROC_BLOCK_SIZE)
+ count = PROC_BLOCK_SIZE;
+ if (!(page = __get_free_page(GFP_KERNEL)))
+ return -ENOMEM;
+ type = inode->i_ino;
+ pid = type >> 16;
+ type &= 0x0000ffff;
+ start = 0;
+ dp = (struct proc_dir_entry *) inode->u.generic_ip;
+ length = mca_fill((char *) page, pid, type,
+ &start, file->f_pos, count);
+ if (length < 0) {
+ free_page(page);
+ return length;
+ }
+ if (start != 0) {
+ /* We have had block-adjusting processing! */
+ copy_to_user(buf, start, length);
+ file->f_pos += length;
+ count = length;
+ } else {
+ /* Static 4kB (or whatever) block capacity */
+ if (file->f_pos >= length) {
+ free_page(page);
+ return 0;
+ }
+ if (count + file->f_pos > length)
+ count = length - file->f_pos;
+ end = count + file->f_pos;
+ copy_to_user(buf, (char *) page + file->f_pos, count);
+ file->f_pos = end;
+ }
+ free_page(page);
+ return count;
+} /* proc_mca_read() */
+
+#endif