Skip site navigation (1)Skip section navigation (2)
Date:      Mon, 15 Feb 1999 00:19:15 +0000 (GMT)
From:      Doug Rabson <dfr@nlsystems.com>
To:        Bill Paul <wpaul@skynet.ctr.columbia.edu>
Cc:        hackers@FreeBSD.ORG, current@FreeBSD.ORG
Subject:   Re: How the hell does one create a new bus?!
Message-ID:  <Pine.BSF.4.05.9902142314140.49128-100000@herring.nlsystems.com>
In-Reply-To: <199902142158.QAA00307@skynet.ctr.columbia.edu>

next in thread | previous in thread | raw e-mail | index | archive | help
On Sun, 14 Feb 1999, Bill Paul wrote:

> Grrrr. Alright, I give up: how does one create a new 'bus' layer?
> By that I mean like /sys/dev/iicbus and /sys/dev/smbus. Yes, I've read
> the code in these two directories, but I'm confused as to which parts
> relate to the bus architecture in general and which parts are speficic
> to the I2C and system management implementation.
> 
> >From what I can tell, you need the following:
> 
> - foobus_if.m, a file which describes the functions that drivers for
>   devices attached to this kind bus can perform.

Each xxx_if.m file defines a set of functions which are implemented by
(some) drivers as you can see.  This can be used in a couple of ways:

1. If a number of different types of the 'bus' hardware can have the same
set of child devices, one can define an interface which each of the
different bus device implements that can be called by the attached child
devices to hide the differences between the hardware.  Code in the drivers
for child devices might look like this:


	/*
         * A ficitious 'probe' method for a device attached to a pci bus.
	 */
	int foo_probe(device_t dev)
	{
		/* Call a function in the PCI interface on our parent */
		if (PCI_READ_CONFIG(device_get_parent(dev),
				    dev,
				    123, 4) == 456)
			return ENXIO;
	}

2. If all the devices attached to a bus can perform similar functions (but
implement them in different ways), an interface can be defined to describe
these functions.  This interface can be called by any client which needs
to use the device.  In this case, each driver would implement the method,
looking something like this:

	/*
	 * Implementation of BOGUS_SET_LEVEL for the foo driver.
	 */
	static void
	foo_set_level(device_t dev, unsigned int level)
	{
		...;
	}

	...

	static device_method_t foo_methods[] = {
			/* Device interface */
		...;

		/* Bogus interface */
		DEVMETHOD(bogus_set_level,	foo_set_level),

		{ 0, 0 }
	};

for an interface declaration (bogus_if.m) which looked like this:

	INTERFACE bogus;

	METHOD void set_level {
		device_t	dev;
		unsigned int	level;
	};

> 
> - foobus.c and foobus.h
>   WHAT EXACTLY GOES IN HERE!?
> 
> - fooconf.c and fooconf.h
>   WHAT EXACTLY GOES IN HERE!?

There really isn't any naming convention for files which make up the
implementation of a driver (a bus is just a driver which implements the
BUS interface and which contains some kind of support for adding child
devices either by a PnP scan or by config(8) supplied tables).

For SMB, the implementation of the smbus driver seems to be in smbus.[ch],
with some kind of utility functions in smbconf.[ch].

To write a file with a 'minimum' driver (in this case a driver named 'foo'
attached to a bus named 'bogus'), you might have something like this:

	struct foo_softc {
		...;
	};

	static devclass_t foo_devclass;
	#define FOO_SOFTC(unit)	(struct foo_softc*) \
		devclass_get_softc(foo_devclass, unit)

	static int
	foo_probe(device_t dev)
	{
		if (device exists)
			return 0;
		else
			return ENXIO;
	}

	static int
	foo_attach(device_t dev)
	{
		device_printf(dev, "I'm alive\n");
		return 0;
	}

	static device_method_t foo_methods[] = {
		/* Device interface */
		DEVMETHOD(device_probe,		foo_probe),
		DEVMETHOD(device_attach,	foo_attach),

		{ 0, 0 }
	};	

	static driver_t foo_driver = {
		"foo",			/* driver name */
		foo_methods,		/* implementation */
		DRIVER_TYPE_NET,	/* interrupt class */
		sizeof(struct foo_softc), /* size of driver scratch */
	};

	DRIVER_MODULE(foo, bogus, foo_driver, foo_devclass, 0, 0);

> 
> - Some type of top level driver for the device which implements the
>   bus.
> 

The implementation of a 'bus' device requires implementing a few extra
methods.  Typically bus_print_child and bus_read_ivar are implemented by
most busses.  Generic implementations for many of the bus functions are
provided (e.g. bus_generic_print_child) but these must be explicitly
specified in the method table (there is no automatic fallback mechanism).

Most bus drivers will add child devices to themselves during either probe
or attach and will call bus_generic_attach() from their attach method (or
just use bus_generic_attach directly instead of having their own
implementation of attach) to probe and attach the child devices.

> - Various drivers for devices attached to the bus.

See above


> 
> In my particular case, I'm trying to create a bus layer for MII-compliant
> PHYs attached to ethernet controller chips. The ethernet controller is
> the bus controller device: each MII management bus can have up to 32
> PHY devices attached to it (though in general you rarely have more than
> two). Each ethernet driver exports three functions to the MII code:
> mii_read(), mii_write() and mii_statchg(). The first two allow reading 
> and writing of the registers of a PHY at a particular address (i.e. 
> mii_read(dev, phyadd, regaddr), mii_write(dev, phyaddr, regaddr, val)). The 
> mii_statchg() routine is a callback routine: when one of the lower-level
> PHY drivers does a media change, it calls back to the ethernet driver
> to let it know. (This is necessary because some media changes also 
> require updating registers on the ethernet controller itself. For 
> instance, on the ThunderLAN chip, if the PHY is set for full duplex, the
> ThunderLAN chip itself also has to be programmed for full duplex at the
> same time).

Here you would define an MII interface (mii_if.m) which describes
mii_read() etc.  Each ethernet driver would contain an implementation of a
driver which implements this interface.  The driver should be called 'mii'
for all ethernet devices since the PHY driver won't know what kind of
hardware they will be attached to.

There is no problem with multiple drivers having the same name as long as
there is no ambiguity.  For instance if each driver was attached to a
different kind of parent device (ethernet device) there is no ambiguity.
If the device probe is performed using accurate PnP data (e.g. pci), then
there is also no ambiguity.

A fraction of the device tree might look like this:


	...
	--------pcib0				The host-pci bridge
		|
		|-------pci0			Top pci bus
			|
			|-------xl0		An ethernet device
			|       |
			|       |-------mii0	and its mii component
			|
			|-------fxp0
			|	|
			|	|-------mii1
			|
			|-------pcib1		A pci-pci bridge
				|
				|-------pci1	With a second pci bus
					|
					|-------de0
					|	|
					|	|-------mii2
					|
					|-------de1
						|
						|-------mii3

Since the code in -current doesn't use the bus interface for pci though,
you will need to fake it out a little by providing a stub driver for each
ethernet driver which attaches an mii device (I'm gradually working on
moving the pci code to the new system but I don't have much FreeBSD time
at the moment):

	root0
	|
	|-------xl0			/* stub driver */
	|	|
	|	|-------mii0
	|
	|-------fxp0
		|
		|-------mii1

The stub probe method for xl0 would create a device for its mii driver to
use:

	xl_stub_probe(device_t dev)
	{
		/*
		 * Add a device with the name 'mii' and allocate a unit
		 * number for it automatically.
		 */
		device_add_child(dev, "mii", -1, NULL);

		return 0;
	}

The stub driver would use bus_generic_attach() as its implementation of
device_attach and would probably use bus_generic_print_child too to help
in printing the probe message.

The real attach method for the xl driver would attach an instance of the
stub device to the root bus to allow the mii to be probed later when the
root bus is probed:

	xl_attach(...)
	{
		...;

		device_add_child(root_bus, "xl", -1, NULL);
	}

> 
> The PHY drivers themselves (as they are now) export three functions to
> the MII glue code: foophy_probe(), foophy_attach() and foophy_service().
> The middle MII layer exports mii_phy_probe(), mii_pollstat(), mii_statchg()
> and mii_tick() back to the ethernet driver.

For the PHY drivers, you would probably define a second interface to
describe the service method and write the drivers as above, possibly
calling MII_XXX methods on the parent device and registering the drivers
under the mii bus:

	DRIVER_MODULE(foophy, mii, foophy_driver, foophy_devclass, 0, 0);

This makes sure that the foophy driver is available to any device
connected to any kind of mii.

> 
> I've faked things up for now using linker sets, but I'd prefer to make
> everything fit into the bus framework if possible. Unfortunately, there 
> doesn't appear to be a nice generic example that shows how to create a 
> new bus layer, and the existing code confuses the hell out of me. For
> example, both the iicbus and smbus code use interrupt routines, but the
> MII code doesn't need interrupts (PHY devices don't normally generate
> interrupts). Also, how do I know exactly what goes into the foobus.c
> and fooconf.c modules? The smbconf.c and iiconf.c modules appear to
> have 'request_bus' routines that do things with tsleep(); what is this
> for? Does every bus need this or is it specific to the I2C and SMB stuff?
> The ppbus code is no help as it doesn't actually use this framework, and
> the usb bus stuff is a mutant pile of macros and #ifdefs.

I think that the request_bus and tsleep stuff is just part of the
implementation of smbus and can be ignored (for your purposes).

> 
> What this stuff needs is a developer's guide; a clearcut explanation of
> how to create new busses, how to create top level bus drivers and how to
> create drivers for devices attached to busses. If anyone can point me at
> such a guide or can just explain to me how this nonsense is supposed to
> work, I'd be grateful.

I hope I've explained some things.  There isn't a developer's guide as
yet, I'm afraid and I don't really have time to write one :-(.  Perhaps
one of the people who has used the code might like to tackle it (or you
might if you manage to struggle through implementing this thing...)

If anyone wants to write such a guide, please go ahead and feel free to
ask me any questions about how the thing is supposed to work.

--
Doug Rabson				Mail:  dfr@nlsystems.com
Nonlinear Systems Ltd.			Phone: +44 181 442 9037



To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe freebsd-current" in the body of the message



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?Pine.BSF.4.05.9902142314140.49128-100000>