patch-2.1.91 linux/drivers/nubus/nubus.c

Next file: linux/drivers/pci/pci.c
Previous file: linux/drivers/nubus/Makefile
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.1.90/linux/drivers/nubus/nubus.c linux/drivers/nubus/nubus.c
@@ -0,0 +1,629 @@
+/*
+ *	Macintosh Nubus Interface Code
+ */
+
+#include <linux/config.h>
+#include <linux/ptrace.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/bios32.h>
+#include <linux/pci.h>
+#include <linux/string.h>
+#include <linux/nubus.h>
+#include <linux/errno.h>
+#include <asm/setup.h>
+#include <asm/system.h>
+#include <asm/page.h>
+/* for LCIII stuff; better find a general way like MACH_HAS_NUBUS */
+#include <asm/macintosh.h>
+
+#undef LCIII_WEIRDNESS
+ 
+static struct nubus_slot nubus_slots[16];
+ 
+/*
+ *	Please skip to the bottom of this file if you ate lunch recently
+ *				-- Alan
+ */
+ 
+ 
+ 
+/* This function tests for the presence of an address, specially a
+ * hardware register address. It is called very early in the kernel
+ * initialization process, when the VBR register isn't set up yet. On
+ * an Atari, it still points to address 0, which is unmapped. So a bus
+ * error would cause another bus error while fetching the exception
+ * vector, and the CPU would do nothing at all. So we needed to set up
+ * a temporary VBR and a vector table for the duration of the test.
+ *
+ * See the atari/config.c code we nicked it from for more clues.
+ */
+
+int nubus_hwreg_present( volatile void *regp )
+{
+    int	ret = 0;
+    long	save_sp, save_vbr;
+    long	tmp_vectors[3];
+    unsigned long flags;
+    
+    save_flags(flags);
+    cli();
+
+    __asm__ __volatile__
+	(	"movec	%/vbr,%2\n\t"
+		"movel	#Lberr1,%4@(8)\n\t"
+                "movec	%4,%/vbr\n\t"
+		"movel	%/sp,%1\n\t"
+		"moveq	#0,%0\n\t"
+		"tstb	%3@\n\t"  
+		"nop\n\t"
+		"moveq	#1,%0\n"
+                "Lberr1:\n\t"
+		"movel	%1,%/sp\n\t"
+		"movec	%2,%/vbr"
+		: "=&d" (ret), "=&r" (save_sp), "=&r" (save_vbr)
+		: "a" (regp), "a" (tmp_vectors)
+                );
+    restore_flags(flags);
+    return( ret );
+}
+
+
+
+/*
+ *	Yes this sucks. The ROM can appear on arbitary bytes of the long
+ *	word. We are not amused.
+ */
+ 
+extern __inline__ int not_useful(void *p, int map)
+{
+	unsigned long pv=(unsigned long)p;
+	pv&=3;
+	if(map&(1<<pv))
+		return 0;
+	return 1;
+}
+ 
+static unsigned long nubus_get_rom(unsigned char **ptr, int len, int map)
+{
+	unsigned long v=0;
+	unsigned char *p=*ptr;	/* as v|=*((*ptr)++) upset someone */
+	while(len)
+	{
+		v<<=8;
+		while(not_useful(p,map))
+			p++;
+		v|=*p++;
+		len--;
+	}
+	*ptr=p;
+	return v;
+}
+
+static void nubus_rewind(unsigned char **ptr, int len, int map)
+{
+	unsigned char *p=*ptr;
+	
+	if(len>8192)
+		printk("rewind of %d!\n", len);
+	while(len)
+	{
+		do
+		{
+			p--;
+		}
+		while(not_useful(p,map));
+		len--;
+	}
+	*ptr=p;
+}
+
+static void nubus_advance(unsigned char **ptr, int len, int map)
+{
+	unsigned char *p=*ptr;
+	if(len>8192)
+		printk("advance of %d!\n", len);
+	while(len)
+	{
+		while(not_useful(p,map))
+			p++;
+			p++;
+		len--;
+	}
+	*ptr=p;
+}
+
+/*
+ *	24bit signed offset to 32bit
+ */
+ 
+static unsigned long nubus_expand32(unsigned long foo)
+{
+	if(foo&0x00800000)	/* 24bit negative */
+		foo|=0xFF000000;
+	return foo;
+}
+
+static void nubus_move(unsigned char **ptr, int len, int map)
+{
+	if(len>0)
+		nubus_advance(ptr,len,map);
+	else if(len<0)
+		nubus_rewind(ptr,-len,map);
+}
+
+static void *nubus_rom_addr(int slot)
+{	
+	/*
+	 *	Returns the first byte after the card. We then walk
+	 *	backwards to get the lane register and the config
+	 */
+	return (void *)(0xF1000000+(slot<<24));
+}
+
+void nubus_memcpy(int slot, void *to, unsigned char *p, int len)
+{
+	unsigned char *t=(unsigned char *)to;
+	while(len)
+	{
+		*t++=nubus_get_rom(&p,1, nubus_slots[slot].slot_lanes);
+		len--;
+	}
+}
+
+void nubus_strncpy(int slot, void *to, unsigned char *p, int len)
+{
+	unsigned char *t=(unsigned char *)to;
+	while(len)
+	{
+		*t=nubus_get_rom(&p,1, nubus_slots[slot].slot_lanes);
+		if(!*t++)
+			break;
+		len--;
+	}
+}
+
+
+
+	
+unsigned char *nubus_dirptr(struct nubus_dirent *nd)
+{
+	unsigned char *p=(unsigned char *)(nd->base);
+	
+	nubus_move(&p, nubus_expand32(nd->value), nd->mask);
+	return p;
+}
+
+	
+struct nubus_dir *nubus_openrootdir(int slot)
+{
+	static struct nubus_dir nbdir;
+	unsigned char *rp=nubus_rom_addr(slot);
+	
+	nubus_rewind(&rp,20, nubus_slots[slot].slot_lanes);
+
+	nubus_move(&rp, nubus_expand32(nubus_slots[slot].slot_directory),
+		nubus_slots[slot].slot_lanes);
+		
+	nbdir.base=rp;
+	nbdir.length=nubus_slots[slot].slot_dlength;
+	nbdir.count=0;
+	nbdir.mask=nubus_slots[slot].slot_lanes;
+	return &nbdir;
+}
+
+struct nubus_dir *nubus_opensubdir(struct nubus_dirent *d)
+{
+	static struct nubus_dir nbdir;
+	unsigned char *rp=nubus_dirptr(d);
+	nbdir.base=rp;
+	nbdir.length=99999;/*slots[i].slot_dlength;*/
+	nbdir.count=0;
+	nbdir.mask=d->mask;
+	return &nbdir;
+}
+
+void nubus_closedir(struct nubus_dir *nd)
+{
+	;
+}
+
+struct nubus_dirent *nubus_readdir(struct nubus_dir *nd)
+{
+	u32 resid;
+	u8 rescode;
+	static struct nubus_dirent d;
+	
+	if(nd->count==nd->length)
+		return NULL;
+
+	d.base=(unsigned long)nd->base;
+		
+	resid=nubus_get_rom(&nd->base, 4, nd->mask);
+	nd->count++;
+	rescode=resid>>24;
+	if(rescode==0xFF)
+	{
+		nd->count=nd->length;
+		return NULL;
+	}
+	d.type=rescode;
+	d.value=resid&0xFFFFFF;
+	d.mask=nd->mask;
+	return &d;
+}
+
+/*
+ *	MAC video handling irritations
+ */
+
+static unsigned char nubus_vid_byte[16];
+static unsigned long nubus_vid_offset[16];
+
+static void nubus_irqsplat(int slot, void *dev_id, struct pt_regs *regs)
+{
+	unsigned char *p=((unsigned char *)nubus_slot_addr(slot))+
+			nubus_vid_offset[slot];
+	*p=nubus_vid_byte[slot];
+}
+
+static int nubus_add_irqsplatter(int slot, unsigned long ptr, unsigned char v)
+{
+	nubus_vid_byte[slot]=v;
+	nubus_vid_offset[slot]=ptr;
+	nubus_request_irq(slot, NULL, nubus_irqsplat);
+	return 0;
+}
+ 
+void nubus_video_shutup(int slot, struct nubus_type *nt)
+{
+	if(nt->category!=3 /* Display */ || nt->type!=1 /* Video */
+		|| nt->DrSW!=1 /* Quickdraw device */)
+		return;
+	switch(nt->DrHW)
+	{
+		/*
+		 *	Toby and MacII Hires cards. These behave in a MacII
+		 *	anyway but not on an RBV box
+		 */
+		case 0x0001:
+		case 0x0013:
+			nubus_add_irqsplatter(slot, 0xA0000, 0);
+			break;
+		/*
+		 *	Apple workstation video card.
+		 */
+		case 0x0006:
+			nubus_add_irqsplatter(slot, 0xA00000, 0);
+			break;
+		/*
+		 *	Futura cards
+		 */
+		case 0x0417:
+		case 0x042F:
+			nubus_add_irqsplatter(slot, 0xF05000, 0x80);
+			break;
+			
+		/*
+		 *	Fingers crossed 8)
+		 *	
+		 *	If you have another card and an RBV based mac you'll
+		 *	almost certainly have to add it here to make it work.
+		 */
+		
+		default:
+			break;
+	}
+}
+
+/*
+ *	Device list
+ */
+
+static struct nubus_device_specifier *nubus_device_list=NULL;
+ 
+void register_nubus_device(struct nubus_device_specifier *d)
+{
+	d->next=nubus_device_list;
+	nubus_device_list=d;
+}
+
+void unregister_nubus_device(struct nubus_device_specifier *nb)
+{
+	struct nubus_device_specifier **t=&nubus_device_list;
+	while(*t!=nb && *t)
+		t=&((*t)->next);
+	*t=nb->next;
+}
+
+static struct nubus_device_specifier *find_nubus_device(int slot, struct nubus_type *nt)
+{
+	struct nubus_device_specifier *t=nubus_device_list; 
+	while(t!=NULL)
+	{
+		if(t->setup(t,slot, nt)==0)
+			return t;
+		t=t->next;
+	}
+	printk("No driver for device [%d %d %d %d]\n",
+		nt->category, nt->type, nt->DrHW, nt->DrSW);
+	return NULL;
+}
+
+/*
+ *	Probe a nubus slot
+ */
+
+void nubus_probe_slot(int slot, int mode)
+{
+	unsigned char *rp;
+	unsigned char dp;
+	int lanes;
+	int i;
+	unsigned long dpat;
+	struct nubus_dir *dir;
+	struct nubus_dirent *nd;
+	struct nubus_type type_info;
+
+	/*
+	 *	Ok see whats cooking in the bytelanes
+	 */
+	
+	rp=nubus_rom_addr(slot);
+	
+	for(i=4;i;i--)
+	{
+		rp--;
+		
+		if(!nubus_hwreg_present(rp))
+			continue;
+			
+		dp=*rp;
+	
+		if(dp==0)
+			continue;
+		
+		/*
+		 *	Valid ?
+		 */
+		 
+		if((((dp>>4)^dp)&0x0F)!=0x0F)
+			continue;
+			
+		if((dp&0x0F) >= 1<<i)
+			continue;
+			
+		/*
+		 *	Looks promising
+		 */
+		
+		nubus_slots[slot].slot_flags|=NUBUS_DEVICE_PRESENT;
+		lanes=dp;
+
+		if (mode==0)
+			printk("nubus%c: ",
+				"0123456789abcdef"[slot]);
+		
+		
+		/*
+		 *	Time to dig deeper. Find the ROM base
+		 *	and read it
+		 */
+		 
+		rp=nubus_rom_addr(slot); 
+		
+		/*
+		 *	Now to make this more fun the ROM is only visible
+		 *	on its bytelanes - that is smeared across the address
+		 *	space.
+		 */
+		
+		nubus_rewind(&rp,20,lanes);
+		
+		nubus_slots[slot].slot_directory=	nubus_get_rom(&rp,4,lanes);
+		nubus_slots[slot].slot_dlength	=	nubus_get_rom(&rp,4,lanes);
+		nubus_slots[slot].slot_crc	=	nubus_get_rom(&rp,4,lanes);
+		nubus_slots[slot].slot_rev	=	nubus_get_rom(&rp,1,lanes);
+		nubus_slots[slot].slot_format	=	nubus_get_rom(&rp,1,lanes);
+		nubus_slots[slot].slot_lanes	=	lanes;
+		
+		dpat=nubus_get_rom(&rp,4,lanes);
+		
+		/*
+		 *	Ok now check what we got
+		 */
+		
+		if(!(nubus_slots[slot].slot_directory&0x00FF0000))
+			printk("Dodgy doffset ??\n");
+		if(dpat!=0x5A932BC7)
+			printk("Wrong test pattern %lx\n",dpat);
+		
+		/*
+		 *	I wonder how the CRC is meant to work -
+		 *		any takers ?
+		 */
+		
+		
+		/*
+		 *	Now parse the directories on the card
+		 */
+		 
+		
+		dir=nubus_openrootdir(slot);
+		
+		/*
+		 *	Find the board resource
+		 */
+		 
+		while((nd=nubus_readdir(dir))!=NULL)
+		{
+			/*
+			 *	This ought to be 1. 1 doesn't work, 0x80
+			 *	does. Seems the Apple docs are wrong.
+			 */
+			if(nd->type==0x80/*RES_ID_BOARD_DIR*/)
+				break;
+		}
+		
+		nubus_closedir(dir);
+		
+		if(nd==NULL)
+		{
+			printk("board resource not found!\n");
+			return;
+		}
+		
+		dir=nubus_opensubdir(nd);
+				
+		/*
+		 *	Walk the board resource
+		 */
+		 
+		while((nd=nubus_readdir(dir))!=NULL)
+		{
+			switch(nd->type)
+			{
+				case RES_ID_TYPE:
+				{
+					unsigned short nbtdata[4];
+					nubus_memcpy(slot, nbtdata, nubus_dirptr(nd), 8);
+					type_info.category=nbtdata[0];
+					type_info.type=nbtdata[1];
+					type_info.DrHW=nbtdata[2];
+					type_info.DrSW=nbtdata[3];
+					break;
+				}
+				case RES_ID_NAME:
+					nubus_strncpy(slot, nubus_slots[slot].slot_cardname,nubus_dirptr(nd),64);
+					break;
+				default:
+					;
+			}
+		}
+		
+		nubus_closedir(dir);
+
+		/*
+		 *	Attempt to bind a driver to this slot
+		 */
+		
+		if (mode==0) {
+			printk("%s\n",
+				nubus_slots[slot].slot_cardname);
+			find_nubus_device(slot,&type_info);
+		}
+		if (mode==1)
+			nubus_video_shutup(slot, &type_info);
+
+		return;
+	}
+}
+
+
+void nubus_probe_bus(void)
+{
+	int i;
+	for(i=9;i<15;i++)
+	{
+		/* printk("nubus: probing slot %d !\n", i); */
+		nubus_probe_slot(i, 0);
+	}
+}
+
+/*
+ *	RBV machines have level triggered video interrupts, and a VIA
+ *	emulation that doesn't always seem to include being able to disable 
+ *	an interrupt. Totally lusing hardware. Before we can init irq's we
+ *	have to install a handler to shut the bloody things up.
+ */
+
+void nubus_sweep_video(void)
+{
+	int i;
+	return; /* XXX why ?? */
+	for(i=9;i<15;i++)
+	{
+		nubus_probe_slot(i,1);
+	}
+}
+
+/*
+ *	Support functions
+ */
+ 
+int nubus_ethernet_addr(int slot, unsigned char *addr)
+{
+	struct nubus_dir *nb;
+	struct nubus_dirent *d;
+	int ng=-ENOENT;
+		
+	nb=nubus_openrootdir(slot);
+	
+	if(nb==NULL)
+		return -ENOENT;
+		
+	while((d=nubus_readdir(nb))!=NULL)
+	{
+		if(d->type==0x80)	/* First private resource */
+			break;
+	}
+	if(d==NULL)
+		return -ENOENT;
+	
+	nb=nubus_opensubdir(d);
+	
+	while((d=nubus_readdir(nb))!=NULL)
+	{
+		if(d->type==0x80)	/* First private field is the mac */
+		{
+			int i;
+			nubus_memcpy(slot, addr, nubus_dirptr(d), 6);
+/*			printk("d.base=%lX, d.value=%lX\n",
+				d->base,d->value);
+			memcpy(addr,"\xC0\xC1\xC2\xC3\xC4\xC5",6);*/
+			printk("MAC address: ");
+			for(i=0;i<6;i++)
+			{
+				printk("%s%02X", i?":":"", addr[i]);
+			}
+			ng=0;
+			break;
+		}
+		else
+			printk("ID=%d val=%x\n",
+				d->type, d->value);
+	}
+	return ng;
+}
+
+void nubus_init(void)
+{
+	/* 
+	 *	Register cards
+	 */
+#ifdef CONFIG_DAYNAPORT
+	extern struct nubus_device_specifier nubus_8390;	
+#endif
+
+	if (!MACH_IS_MAC) 
+		return;
+
+#ifdef LCIII_WEIRDNESS
+	if (macintosh_config->ident == MAC_MODEL_LCIII) {
+	        printk("nubus init: LCIII has no nubus!\n");
+		return;
+	}
+#endif
+
+#ifdef CONFIG_DAYNAPORT
+	register_nubus_device(&nubus_8390);
+#endif
+
+	/*
+	 *	And probe
+	 */
+	 
+	nubus_init_via();
+	printk("Scanning nubus slots.\n");
+	nubus_probe_bus();
+}

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov