/* * $Id$ * * Copyright (C) 2003-2015 JNode.org * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; If not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.jnode.driver; import gnu.java.security.action.GetPropertyAction; import java.security.AccessController; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.jnode.bootlog.BootLogInstance; import org.jnode.naming.InitialNaming; import org.jnode.plugin.PluginException; import org.jnode.util.StopWatch; /** * Default device manager. * * @author epr */ public abstract class AbstractDeviceManager implements DeviceManager { /** * All registered devices. */ private final Map<String, Device> devices = new HashMap<String, Device>(); /** * All registered device to driver mappers. */ private final List<DeviceToDriverMapper> mappers = new ArrayList<DeviceToDriverMapper>(); /** * All registered device finders. */ private final List<DeviceFinder> finders = new ArrayList<DeviceFinder>(); /** * All listeners to my events. */ private final List<DeviceManagerListener> listeners = new LinkedList<DeviceManagerListener>(); /** * All listeners to device events. */ private final List<DeviceListener> deviceListeners = new LinkedList<DeviceListener>(); /** * The system bus. */ private final Bus systemBus; /** * The JNode command line. */ private final String cmdLine; private boolean extensionsLoaded = false; private long defaultStartTimeout = 10000; private long fastStartTimeout = 1000; /** * Create a new instance. */ public AbstractDeviceManager() { this((String) AccessController.doPrivileged(new GetPropertyAction( "jnode.cmdline", ""))); } /** * Create a new instance. * @param commandLine command line or an empty string */ protected AbstractDeviceManager(String commandLine) { this.cmdLine = commandLine; this.systemBus = new SystemBus(); } /** * Returns a collection of all known devices. The collection is not * modifiable, but the underlying collection can change, so be aware of * exceptions in iterators. * * @return All known devices. */ public final Collection<Device> getDevices() { return Collections.unmodifiableCollection(devices.values()); } /** * Returns a collection of all known devices that implement the given api.. * The collection is not modifiable, but the underlying collection can * change, so be aware of exceptions in iterators. * * @param apiClass * @return All known devices the implement the given api. */ public final Collection<Device> getDevicesByAPI(Class<? extends DeviceAPI> apiClass) { final ArrayList<Device> result = new ArrayList<Device>(); for (Device dev : devices.values()) { if (dev.implementsAPI(apiClass)) { result.add(dev); } } return result; } /** * Gets the device with the given ID. * * @param id * @return The device with the given id * @throws DeviceNotFoundException No device with the given id was found. */ public final Device getDevice(String id) throws DeviceNotFoundException { final Device device = devices.get(id); if (device == null) { throw new DeviceNotFoundException(id); } return device; } /** * Register a new device. This involves the following steps: * <ul> * <li>Search for a suitable driver for the device. If not found the driver * startup is delayed. * <li>Connect the driver to the device, if a driver is found * <li>Attempt to start the device. If this fails an exception is printed * in the log. You can test if the device was started successfully, by reading * the <code>isStarted</code> status. * </ul> * Note that if the device already has a driver connected to it, the first * two steps are ignored. * * @param device * @throws DeviceAlreadyRegisteredException * * @throws DriverException */ public final void register(Device device) throws DeviceAlreadyRegisteredException, DriverException { boolean shouldStart; // Perform the actual registration. shouldStart = doRegister(device); // Test for no<id> on the command line if (cmdLine.indexOf("no" + device.getId()) >= 0) { BootLogInstance.get().info("Blocking the start of " + device.getId()); shouldStart = false; } // Notify my listeners fireRegisteredEvent(device); // Should we start the device? if (shouldStart) { // Try to start the device try { start(device); } catch (DeviceNotFoundException ex) { // Should not happen BootLogInstance.get().error("Device removed before being started", ex); } } } /** * Actually register the device. The device is not started, nor is the * registered event fired. * * @param device * @return true if the device should be tried to start. * @throws DeviceAlreadyRegisteredException * * @throws DriverException */ private synchronized boolean doRegister(Device device) throws DeviceAlreadyRegisteredException, DriverException { final String devID = device.getId(); if (devices.containsKey(devID)) { throw new DeviceAlreadyRegisteredException( devID); } // Set a link to me device.setManager(this); // Find a driver if needed boolean shouldStart = true; if (device.getDriver() == null) { final Driver drv = findDriver(device); if (drv == null) { shouldStart = false; } else { // Connect the device to the driver device.setDriver(drv); } } // Add the device to my list devices.put(device.getId(), device); // We're done return shouldStart; } /** * Unregister a device. The device will be stopped and removed from the * namespace. * * @param device * @throws DriverException */ public final void unregister(Device device) throws DriverException { // First stop the device if it is running try { stop(device); // Notify my listeners fireUnregisterEvent(device); // Actually remove it synchronized (this) { devices.remove(device.getId()); } } catch (DeviceNotFoundException ex) { // Not found, so stop BootLogInstance.get().debug("Device not found in unregister"); } } /** * Start a given device. The device must have been registered. * * @param device * @throws DeviceNotFoundException The device has not been registered. * @throws DriverException */ public final void start(Device device) throws DeviceNotFoundException, DriverException { // Make sure the device exists. getDevice(device.getId()); // Start it (if needed) if (!device.isStarted()) { try { BootLogInstance.get().debug("Starting " + device.getId()); //new DeviceStarter(device).start(getDefaultStartTimeout()); final StopWatch sw = new StopWatch(); device.start(); sw.stop(); if (sw.isElapsedLongerThen(defaultStartTimeout)) { BootLogInstance.get().error("Device startup took " + sw + ": " + device.getId()); } else if (sw.isElapsedLongerThen(fastStartTimeout)) { BootLogInstance.get().info("Device startup took " + sw + ": " + device.getId()); } BootLogInstance.get().debug("Started " + device.getId()); } catch (DriverException ex) { BootLogInstance.get().error("Cannot start " + device.getId(), ex); //} catch (TimeoutException ex) { // BootLogInstance.get().warn("Timeout in start of " + device.getId()); } catch (Throwable ex) { BootLogInstance.get().error("Cannot start " + device.getId(), ex); } } } /** * Stop a given device. The device must have been registered. * * @param device * @throws DeviceNotFoundException The device has not been registered. * @throws DriverException */ public final void stop(Device device) throws DeviceNotFoundException, DriverException { // Make sure the device exists. getDevice(device.getId()); // Stop it if (device.isStarted()) { BootLogInstance.get().debug("Starting " + device.getId()); device.stop(false); BootLogInstance.get().debug("Stopped " + device.getId()); } } /** * Rename a device, optionally using an autonumber postfix. * * @param device * @param name * @param autonumber * @throws DeviceAlreadyRegisteredException * */ public final synchronized void rename(Device device, String name, boolean autonumber) throws DeviceAlreadyRegisteredException { if (!device.getId().startsWith(name)) { String newId; if (autonumber) { int cnt = 0; newId = name + cnt; while (devices.containsKey(newId)) { cnt++; newId = name + cnt; } } else { newId = name; } if (devices.containsKey(newId)) { throw new DeviceAlreadyRegisteredException( newId); } // Remove the old id if (devices.remove(device.getId()) != null) { // Add the new id devices.put(newId, device); } // Change the device id device.setId(newId); } } /** * Add a listener. * * @param listener */ public final void addListener(DeviceManagerListener listener) { synchronized (listeners) { listeners.add(listener); } } /** * Add a listener. * * @param listener */ public final void removeListener(DeviceManagerListener listener) { synchronized (listeners) { listeners.remove(listener); } } /** * Add a device listener. * * @param listener */ public final void addListener(DeviceListener listener) { synchronized (deviceListeners) { deviceListeners.add(listener); } } /** * Add a device listener. * * @param listener */ public final void removeListener(DeviceListener listener) { synchronized (deviceListeners) { deviceListeners.remove(listener); } } /** * Stop all devices. */ public final void stopDevices() { while (!devices.isEmpty()) { final Device dev = (Device) devices.values().iterator().next(); try { BootLogInstance.get().debug("Stopping device " + dev.getId()); unregister(dev); } catch (DriverException ex) { BootLogInstance.get().error("Failed to stop device " + dev.getId(), ex); } } } /** * Gets the system bus. The system bus is the root of all hardware busses * and devices connected to these buses. * * @return The system bus */ public final Bus getSystemBus() { return systemBus; } /** * Find a driver from each device that has not yet has a driver connected to * it. */ protected final void findDeviceDrivers() { final List<Device> devices; synchronized (this) { devices = new ArrayList<Device>(this.devices.values()); } for (Device dev : devices) { if (dev.getDriver() == null) { final Driver drv = findDriver(dev); if (drv != null) { try { dev.setDriver(drv); start(dev); } catch (DriverException ex) { BootLogInstance.get().error("Cannot start " + dev.getId(), ex); } catch (DeviceNotFoundException ex) { // Should not happen BootLogInstance.get().error("Device is gone before is can be started " + dev.getId(), ex); } } } } } /** * Use all device finders to find all system devices. */ protected final void findDevices() throws InterruptedException { waitUntilExtensionsLoaded(); final ArrayList<DeviceFinder> finders; synchronized (this) { finders = new ArrayList<DeviceFinder>(this.finders); } for (DeviceFinder finder : finders) { try { finder.findDevices(this, systemBus); } catch (DeviceException ex) { BootLogInstance.get().error("Error while trying to find system devices", ex); } catch (RuntimeException ex) { BootLogInstance.get() .error( "Runtime exception while trying to find system devices", ex); } } } /** * Search for a suitable driver for the given device. * * @param device * @return The first suitable driver for the given device, or a NullDriver * if no suitable driver has been found. */ protected final Driver findDriver(Device device) { synchronized (mappers) { for (DeviceToDriverMapper mapper : mappers) { final Driver drv = mapper.findDriver(device); if (drv != null) { //Syslog.debug("Found driver for " + device); return drv; } } } BootLogInstance.get().debug("No driver found for " + device + " delaying device startup"); return null; } /** * Start this manager. * * @throws PluginException */ public abstract void start() throws PluginException; protected final synchronized void loadExtensions() { refreshFinders(finders); refreshMappers(mappers); extensionsLoaded = true; notifyAll(); } private synchronized void waitUntilExtensionsLoaded() throws IllegalMonitorStateException, InterruptedException { while (!extensionsLoaded) { wait(); } } /** * Stop this manager. * * @throws PluginException */ public final void stop() throws PluginException { stopDevices(); InitialNaming.unbind(NAME); } /** * Fire a deviceRegistered event to all my listeners * * @param device */ protected final void fireRegisteredEvent(Device device) { final List<DeviceManagerListener> list; synchronized (this.listeners) { list = new ArrayList<DeviceManagerListener>(this.listeners); } final StopWatch sw = new StopWatch(); for (DeviceManagerListener l : list) { sw.start(); l.deviceRegistered(device); if (sw.isElapsedLongerThen(100)) { BootLogInstance.get().error("DeviceManagerListener took " + sw + " in deviceRegistered: " + l.getClass().getName()); } } } /** * Fire a deviceUnregister event to all my listeners. * * @param device */ protected final void fireUnregisterEvent(Device device) { final List<DeviceManagerListener> list; synchronized (this.listeners) { list = new ArrayList<DeviceManagerListener>(this.listeners); } final StopWatch sw = new StopWatch(); for (DeviceManagerListener l : list) { sw.start(); l.deviceUnregister(device); if (sw.isElapsedLongerThen(100)) { BootLogInstance.get().error("DeviceManagerListener took " + sw + " in deviceUnregister: " + l.getClass().getName()); } } } /** * Fire a device started event to all the device listeners * * @param device */ public final void fireStartedEvent(Device device) { final List<DeviceListener> list; synchronized (this.deviceListeners) { list = new ArrayList<DeviceListener>(this.deviceListeners); } final StopWatch sw = new StopWatch(); for (DeviceListener l : list) { sw.start(); l.deviceStarted(device); if (sw.isElapsedLongerThen(100)) { BootLogInstance.get().error("DeviceListener (in manager) took " + sw + " in deviceStarted: " + l.getClass().getName()); } } } /** * Fire a device stop event to all the device listeners * * @param device */ public final void fireStopEvent(Device device) { final List<DeviceListener> list; synchronized (this.deviceListeners) { list = new ArrayList<DeviceListener>(this.deviceListeners); } final StopWatch sw = new StopWatch(); for (DeviceListener l : list) { sw.start(); l.deviceStop(device); if (sw.isElapsedLongerThen(100)) { BootLogInstance.get().error("DeviceListener (in manager) took " + sw + " in deviceStop: " + l.getClass().getName()); } } } /** * Refresh the list of finders, based on the mappers extension-point. * * @param finders */ protected abstract void refreshFinders(List<DeviceFinder> finders); /** * Refresh the list of mappers, based on the mappers extension-point. * * @param mappers */ protected abstract void refreshMappers(List<DeviceToDriverMapper> mappers); /** * The root bus of every system. * * @author Ewout Prangsma (epr@users.sourceforge.net) */ static class SystemBus extends Bus { } /** * Comparator used to sort {@link org.jnode.driver.DeviceToDriverMapper DeviceToDriverMapper}s. * * @author Ewout Prangsma (epr@users.sourceforge.net) */ protected static class MapperComparator implements Comparator<DeviceToDriverMapper> { public static final MapperComparator INSTANCE = new MapperComparator(); /** * @param m1 * @param m2 * @return int * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) */ public int compare(DeviceToDriverMapper m1, DeviceToDriverMapper m2) { final int ml1 = m1.getMatchLevel(); final int ml2 = m2.getMatchLevel(); if (ml1 < ml2) { return -1; } else if (ml1 == ml2) { return 0; } else { return 1; } } } /** * Gets the default timeout for device startup. * * @return Returns the defaultStartTimeout. */ public final long getDefaultStartTimeout() { return this.defaultStartTimeout; } /** * Sets the default timeout for device startup. * * @param defaultStartTimeout The defaultStartTimeout to set. */ public final void setDefaultStartTimeout(long defaultStartTimeout) { this.defaultStartTimeout = defaultStartTimeout; } }