📄 ocp.c
字号:
/* * ocp.c * * (c) Benjamin Herrenschmidt (benh@kernel.crashing.org) * Mipsys - France * " Derived from work (c) Armin Kuster akuster@pacbell.net * * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 675 Mass Ave, Cambridge, MA 02139, USA. * */#include <linux/module.h>#include <linux/config.h>#include <linux/list.h>#include <linux/miscdevice.h>#include <linux/slab.h>#include <linux/types.h>#include <linux/init.h>#include <linux/pm.h>#include <linux/bootmem.h>#include <asm/io.h>#include <asm/ocp.h>#include <asm/errno.h>#include <asm/rwsem.h>#include <asm/semaphore.h>//#define DBG(x) printk x#define DBG(x)extern int mem_init_done;extern struct ocp_def core_ocp[]; /* Static list of devices, provided by CPU core */LIST_HEAD(ocp_devices); /* List of all OCP devices */LIST_HEAD(ocp_drivers); /* List of all OCP drivers */DECLARE_RWSEM(ocp_devices_sem); /* Global semaphores for those lists */DECLARE_MUTEX(ocp_drivers_sem); /* Global semaphores for those lists */static int ocp_inited;/** * ocp_driver_match - Match one driver to one device * @drv: driver to match * @dev: device to match * * This function returns 0 if the driver and device don't match */static intocp_driver_match(struct ocp_driver *drv, struct ocp_device *dev){ const struct ocp_device_id *ids = drv->id_table; if (!ids) return 0; while (ids->vendor || ids->function) { if ((ids->vendor == OCP_ANY_ID || ids->vendor == dev->def->vendor) && (ids->function == OCP_ANY_ID || ids->function == dev->def->function)) return 1; ids++; } return 0;}/** * ocp_bind_drivers - Match all drivers with all devices * @candidate: driver beeing registered * * This function is called on driver registration and device discovery, * it redo the matching of all "driverless" devices with all possible * driver candidates. * The driver beeing registered can be optionally passed in, in which * case, the function will return -ENODEV is no match have been found * or if all matches failed with a different code than -EAGAIN */static intocp_bind_drivers(struct ocp_driver *candidate){ struct list_head *deventry, *drventry; struct ocp_device *dev; struct ocp_driver *drv; int one_again, one_match; int count = 0; DBG(("ocp: binding drivers...\n")); do { /* We re-do the match loop if we had a sucess match and got one -EAGAIN */ one_match = one_again = 0; down_read(&ocp_devices_sem); list_for_each(deventry, &ocp_devices) { dev = list_entry(deventry, struct ocp_device, link); if (dev->driver != NULL) continue; DBG(("ocp: device %s unmatched, trying to match...\n", dev->name)); list_for_each(drventry, &ocp_drivers) { drv = list_entry(drventry, struct ocp_driver, link); if (ocp_driver_match(drv, dev)) { int rc; /* Hrm... shall we set dev->driver after or before ? */ DBG(("ocp: match with driver %s, calling probe...\n", drv->name)); rc = drv->probe(dev); DBG(("ocp: probe result: %d\n", rc)); if (rc == 0) { /* Driver matched, next device */ dev->driver = drv; one_match = 1; if (drv == candidate) count++; break; } else if (rc == -EAGAIN) { /* Driver matched but asked for later call, next device */ one_again = 1; if (drv == candidate) count++; break; } } } } up_read(&ocp_devices_sem); } while(one_match && one_again); DBG(("ocp: binding drivers... done.\n")); return count;}/** * ocp_register_driver - Register an OCP driver * @drv: pointer to statically defined ocp_driver structure * * The driver's probe() callback is called either recursively * by this function or upon later call of ocp_driver_init * * NOTE: Probe is called with ocp_drivers_sem held, it shouldn't * call ocp_register/unregister_driver on his own code path. * however, it _can_ call ocp_find_device(). * * NOTE2: Detection of devices is a 2 pass step on this implementation, * hotswap isn't supported. First, all OCP devices are put in the device * list, _then_ all drivers are probed on each match. * * NOTE3: Drivers are allowed to return -EAGAIN from the probe() routine. * this will cause them to be called again for this specific device as * soon as another device have been probed or another driver registered. * this, gives a simple way for a driver like EMAC to wait for another driver, * like MAL to be up. There is potentially a small race if MAL happens to * unregister, but this is hopefully never happening. * * This function returns a count of how many devices actually matched * (wether the probe routine returned 0 or -EAGAIN, a different error * code isn't considered as a match). */intocp_register_driver(struct ocp_driver *drv){ int rc = 0; DBG(("ocp: ocp_register_driver(%s)...\n", drv->name)); /* Add to driver list */ down(&ocp_drivers_sem); list_add_tail(&drv->link, &ocp_drivers); /* Check matching devices */ rc = ocp_bind_drivers(drv); up(&ocp_drivers_sem); DBG(("ocp: ocp_register_driver(%s)... done, count: %d.\n", drv->name, rc)); return rc;}/** * ocp_unregister_driver - Unregister an OCP driver * @drv: pointer to statically defined ocp_driver structure * * The driver's remove() callback is called recursively * by this function for any device already registered */voidocp_unregister_driver(struct ocp_driver *drv){ struct ocp_device *dev; struct list_head *entry; DBG(("ocp: ocp_unregister_driver(%s)...\n", drv->name)); /* Call remove() routine for all devices using it */ down(&ocp_drivers_sem); down_read(&ocp_devices_sem); list_for_each(entry, &ocp_devices) { dev = list_entry(entry, struct ocp_device, link); if (dev->driver == drv) { drv->remove(dev); dev->driver = NULL; dev->drvdata = NULL; } } up_read(&ocp_devices_sem); /* Unlink driver structure */ list_del_init(&drv->link); up(&ocp_drivers_sem); DBG(("ocp: ocp_unregister_driver(%s)... done.\n", drv->name));}/* Core of ocp_find_device(). Caller must hold ocp_devices_sem */static struct ocp_device *__ocp_find_device(unsigned int vendor, unsigned int function, int index){ struct list_head *entry; struct ocp_device *dev, *found = NULL; DBG(("ocp: __ocp_find_device(vendor: %x, function: %x, index: %d)...\n", vendor, function, index)); list_for_each(entry, &ocp_devices) { dev = list_entry(entry, struct ocp_device, link); if (vendor != OCP_ANY_ID && vendor != dev->def->vendor) continue; if (function != OCP_ANY_ID && function != dev->def->function) continue; if (index != OCP_ANY_INDEX && index != dev->def->index) continue; found = dev; break; } DBG(("ocp: __ocp_find_device(vendor: %x, function: %x, index: %d)... done\n", vendor, function, index)); return found;}/** * ocp_find_device - Find a device by function & index * @vendor: vendor ID of the device (or OCP_ANY_ID) * @function: function code of the device (or OCP_ANY_ID) * @idx: index of the device (or OCP_ANY_INDEX) * * This function allows a lookup of a given function by it's * index, it's typically used to find the MAL or ZMII associated * with an EMAC or similar horrors. * You can pass vendor, though you usually want OCP_ANY_ID there... */struct ocp_device *ocp_find_device(unsigned int vendor, unsigned int function, int index){ struct ocp_device *dev; down_read(&ocp_devices_sem); dev = __ocp_find_device(vendor, function, index);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -