[BACK]Return to device.c CVS log [TXT][DIR] Up to [local] / prex-old / sys / kern

File: [local] / prex-old / sys / kern / device.c (download)

Revision 1.1, Tue Jun 3 09:38:46 2008 UTC (15 years, 10 months ago) by nbrk
Branch point for: MAIN

Initial revision

/*-
 * Copyright (c) 2005-2007, Kohsuke Ohtani
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the author nor the names of any co-contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
 */

/*
 * device.c - device I/O support routines
 */

/*
 * The device_* system calls are interfaces for user mode applications
 * to access the specific device object which is handled by the related
 * device driver. A device driver is an execution module different from
 * a kernel on Prex. The routines in this file have the following role
 * to handle the device I/O.
 *
 *  - Manage the name space for device objects.
 *  - Forward user I/O requests to the drivers after checking parameters.
 *
 * The driver module(s) and kernel are dynamically linked at system boot.
 */

#include <kernel.h>
#include <irq.h>
#include <page.h>
#include <kmem.h>
#include <task.h>
#include <timer.h>
#include <sched.h>
#include <exception.h>
#include <vm.h>
#include <device.h>
#include <system.h>

/* forward declarations */
static device_t device_create(struct devio *, const char *, int);
static int device_destroy(device_t);
static int device_broadcast(int, int);
static void machine_bootinfo(struct boot_info **);
static void machine__reset(void);
static void machine__idle(void);
static int task__capable(cap_t cap);
static void *phys__to_virt(void *);
static void *virt__to_phys(void *);

#ifndef DEBUG
static void nosys(void);
#undef printk
#define printk nosys

#undef panic
#define panic machine_reset
#endif

typedef void (*dkifn_t)(void);

#define DKIENT(func)	(dkifn_t)(func)

/*
 * Driver-Kernel Interface (DKI)
 */
static const dkifn_t driver_service[] = {
	/*  0 */ DKIENT(device_create),
	/*  1 */ DKIENT(device_destroy),
	/*  2 */ DKIENT(device_broadcast),
	/*  3 */ DKIENT(umem_copyin),
	/*  4 */ DKIENT(umem_copyout),
	/*  5 */ DKIENT(umem_strnlen),
	/*  6 */ DKIENT(kmem_alloc),
	/*  7 */ DKIENT(kmem_free),
	/*  8 */ DKIENT(kmem_map),
	/*  9 */ DKIENT(page_alloc),
	/* 10 */ DKIENT(page_free),
	/* 11 */ DKIENT(page_reserve),
	/* 12 */ DKIENT(irq_attach),
	/* 13 */ DKIENT(irq_detach),
	/* 14 */ DKIENT(irq_lock),
	/* 15 */ DKIENT(irq_unlock),
	/* 16 */ DKIENT(timer_callout),
	/* 17 */ DKIENT(timer_stop),
	/* 18 */ DKIENT(timer_delay),
	/* 19 */ DKIENT(timer_count),
	/* 20 */ DKIENT(timer_hook),
	/* 21 */ DKIENT(sched_lock),
	/* 22 */ DKIENT(sched_unlock),
	/* 23 */ DKIENT(sched_tsleep),
	/* 24 */ DKIENT(sched_wakeup),
	/* 25 */ DKIENT(sched_dpc),
	/* 26 */ DKIENT(task__capable),
	/* 27 */ DKIENT(exception_post),
	/* 28 */ DKIENT(machine_bootinfo),
	/* 29 */ DKIENT(machine__reset),
	/* 30 */ DKIENT(machine__idle),
	/* 31 */ DKIENT(phys__to_virt),
	/* 32 */ DKIENT(virt__to_phys),
	/* 33 */ DKIENT(debug_attach),
	/* 34 */ DKIENT(debug_dump),
	/* 35 */ DKIENT(printk),
	/* 36 */ DKIENT(panic),
};

static struct list device_list;		/* list of the device objects */

/*
 * Increment reference count on an active device.
 * This routine checks whether the specified device is valid.
 * It returns 0 on success, or -1 on failure.
 */
static int
device_hold(device_t dev)
{
	int err = -1;

	sched_lock();
	if (device_valid(dev)) {
		dev->ref_count++;
		err = 0;
	}
	sched_unlock();
	return err;
}

/*
 * Decrement the reference count on a device. If the reference
 * count becomes zero, we can release the resource for the
 * target device. Assumes the device is already validated by caller.
 */
static void
device_release(device_t dev)
{

	sched_lock();
	if (--dev->ref_count == 0) {
		list_remove(&dev->link);
		kmem_free(dev);
	}
	sched_unlock();
}

/*
 * Look up a device object by device name.
 * Return device ID on success, or NULL on failure.
 * This must be called with scheduler locked.
 */
static device_t
device_lookup(const char *name)
{
	list_t head, n;
	device_t dev;

	if (name == NULL)
		return NULL;

	head = &device_list;
	for (n = list_first(head); n != head; n = list_next(n)) {
		dev = list_entry(n, struct device, link);
		if (!strncmp(dev->name, name, MAXDEVNAME))
			return dev;
	}
	return NULL;
}

/*
 * device_create - create new device object.
 * @io:    pointer to device I/O routines
 * @name:  string for device name
 * @flags: flags for device object. (ex. block or character)
 *
 * A device object is created by the device driver to provide
 * I/O services to applications.
 * Returns device ID on success, or 0 on failure.
 */
static device_t
device_create(struct devio *io, const char *name, int flags)
{
	device_t dev;
	size_t len;

	ASSERT(irq_level == 0);

	len = strnlen(name, MAXDEVNAME);
	if (len == 0 || len >= MAXDEVNAME)	/* Invalid name? */
		return 0;

	sched_lock();
	if ((dev = device_lookup(name)) != NULL) {
		/*
		 * Error - the device name is already used.
		 */
		sched_unlock();
		return 0;
	}
	if ((dev = kmem_alloc(sizeof(struct device))) == NULL) {
		sched_unlock();
		return 0;
	}
	strlcpy(dev->name, name, len + 1);
	dev->devio = io;
	dev->flags = flags;
	dev->ref_count = 1;
	dev->magic = DEVICE_MAGIC;
	list_insert(&device_list, &dev->link);
	sched_unlock();
	return dev;
}

/*
 * Destroy a device object. If some other threads still refer
 * the target device, the destroy operating will be pending
 * until its reference count becomes 0.
 */
static int
device_destroy(device_t dev)
{
	int err = 0;

	ASSERT(irq_level == 0);

	sched_lock();
	if (device_valid(dev))
		device_release(dev);
	else
		err = ENODEV;
	sched_unlock();
	return err;
}

/*
 * device_open - open the specified device.
 * @name: device name (null-terminated)
 * @mode: open mode. (like O_RDONLY etc.)
 * @devp: device handle of opened device to be returned.
 *
 * Even if the target driver does not have an open routine, this
 * function does not return an error. By using this mechanism, an
 * application can check whether the specific device exists or not.
 * The open mode should be handled by an each device driver if it
 * is needed.
 */
int
device_open(const char *name, int mode, device_t *devp)
{
	char str[MAXDEVNAME];
	device_t dev;
	size_t len;
	int err = 0;

	if (!task_capable(CAP_DEVIO))
		return EPERM;

	if (umem_strnlen(name, MAXDEVNAME, &len))
		return EFAULT;
	if (len == 0)
		return ENOENT;
	if (len >= MAXDEVNAME)
		return ENAMETOOLONG;

	if (umem_copyin((void *)name, str, len + 1))
		return EFAULT;

	sched_lock();
	if ((dev = device_lookup(str)) == NULL) {
		sched_unlock();
		return ENXIO;
	}
	device_hold(dev);
	sched_unlock();

	if (dev->devio->open != NULL)
		err = (dev->devio->open)(dev, mode);

	if (!err)
		err = umem_copyout(&dev, devp, sizeof(device_t));
	device_release(dev);
	return err;
}

/*
 * device_close - close a device.
 *
 * Even if the target driver does not have close routine,
 * this function does not return any errors.
 */
int
device_close(device_t dev)
{
	int err = 0;

	if (!task_capable(CAP_DEVIO))
		return EPERM;

	if (device_hold(dev))
		return ENODEV;

	if (dev->devio->close != NULL)
		err = (dev->devio->close)(dev);

	device_release(dev);
	return err;
}

/*
 * device_read - read from a device.
 * @dev:   device id
 * @buf:   pointer to read buffer
 * @nbyte: number of bytes to read. actual read count is set in return.
 * @blkno: block number (for block device)
 *
 * Note: The size of one block is device dependent.
 */
int
device_read(device_t dev, void *buf, size_t *nbyte, int blkno)
{
	size_t count;
	int err;

	if (!task_capable(CAP_DEVIO))
		return EPERM;

	if (device_hold(dev))
		return ENODEV;

	if (dev->devio->read == NULL) {
		device_release(dev);
		return EBADF;
	}
	if (umem_copyin(nbyte, &count, sizeof(u_long)) ||
	    vm_access(buf, count, VMA_WRITE)) {
		device_release(dev);
		return EFAULT;
	}
	err = (dev->devio->read)(dev, buf, &count, blkno);
	if (err == 0)
		err = umem_copyout(&count, nbyte, sizeof(u_long));
	device_release(dev);
	return err;
}

/*
 * device_write - write to a device.
 * @dev:   device id
 * @buf:   pointer to write buffer
 * @nbyte: number of bytes to write. actual write count is set in return.
 * @blkno: block number (for block device)
 */
int
device_write(device_t dev, void *buf, size_t *nbyte, int blkno)
{
	size_t count;
	int err;

	if (!task_capable(CAP_DEVIO))
		return EPERM;

	if (device_hold(dev))
		return ENODEV;

	if (dev->devio->write == NULL) {
		device_release(dev);
		return EBADF;
	}
	if (umem_copyin(nbyte, &count, sizeof(u_long)) ||
	    vm_access(buf, count, VMA_READ)) {
		device_release(dev);
		return EFAULT;
	}
	err = (dev->devio->write)(dev, buf, &count, blkno);
	if (err == 0)
		err = umem_copyout(&count, nbyte, sizeof(u_long));

	device_release(dev);
	return err;
}

/*
 * deivce_ioctl - I/O control request.
 * @dev: device id
 * @cmd: command
 * @arg: argument
 *
 * A command and an argument are completely device dependent.
 * If argument type is pointer, the driver routine must validate
 * the pointer address.
 */
int
device_ioctl(device_t dev, int cmd, u_long arg)
{
	int err;

	if (!task_capable(CAP_DEVIO))
		return EPERM;

	if (device_hold(dev))
		return ENODEV;

	err = EBADF;
	if (dev->devio->ioctl != NULL)
		err = (dev->devio->ioctl)(dev, cmd, arg);

	device_release(dev);
	return err;
}

/*
 * device_broadcast - broadcast an event to all device objects.
 * @event: event code
 * @force: true to ignore the return value from driver.
 *
 * If force argument is true, a kernel will continue event
 * notification even if some driver returns error. In this case,
 * this routine returns EIO error if at least one driver returns
 * an error.
 *
 * If force argument is false, a kernel stops the event processing
 * when at least one driver returns an error. In this case,
 * device_broadcast will return the error code which is returned
 * by the driver.
 */
static int
device_broadcast(int event, int force)
{
	device_t dev;
	list_t head, n;
	int err, ret = 0;

	sched_lock();
	head = &device_list;

#ifdef DEBUG
	printk("Broadcasting device event:%d\n", event);
#endif
	for (n = list_first(head); n != head; n = list_next(n)) {
		dev = list_entry(n, struct device, link);
		if (dev->devio->event == NULL)
			continue;

		err = (dev->devio->event)(event);
		if (err) {
			if (force)
				ret = EIO;
			else {
				ret = err;
				break;
			}
		}
	}
	sched_unlock();
	return ret;
}

/*
 * Return device information (for devfs).
 */
int
device_info(struct info_device *info)
{
	u_long index, target = info->cookie;
	device_t dev;
	struct devio *io;
	list_t head, n;

	sched_lock();

	index = 0;
	head = &device_list;
	for (n = list_first(head); n != head; n = list_next(n), index++) {
		dev = list_entry(n, struct device, link);
		io = dev->devio;
		if (index == target)
			break;
	}
	if (n == head) {
		sched_unlock();
		return ESRCH;
	}
	info->id = dev;
	info->flags = dev->flags;
	strlcpy(info->name, dev->name, MAXDEVNAME);

	sched_unlock();
	return 0;
}

#if defined(DEBUG) && defined(CONFIG_KDUMP)
void
device_dump(void)
{
	device_t dev;
	struct devio *io;
	list_t head, n;

	printk("Device dump:\n");
	printk(" device   open     close    read     write    ioctl    "
	       "event    name\n");
	printk(" -------- -------- -------- -------- -------- -------- "
	       "-------- ------------\n");

	head = &device_list;
	for (n = list_first(head); n != head; n = list_next(n)) {
		dev = list_entry(n, struct device, link);
		io = dev->devio;
		printk(" %08x %08x %08x %08x %08x %08x %08x %s\n",
		       dev, io->open, io->close, io->read, io->write,
		       io->ioctl, io->event, dev->name);
	}
}
#endif

#ifndef DEBUG
static void
nosys(void)
{
}
#endif

/*
 * Check the capability of the current task.
 */
static int
task__capable(cap_t cap)
{

	return task_capable(cap);
}

/*
 * Return boot information
 */
static void
machine_bootinfo(struct boot_info **info)
{
	ASSERT(info != NULL);

	*info = boot_info;
}

static void
machine__reset(void)
{

	machine_reset();
}

static void
machine__idle(void)
{

	machine_idle();
}

/*
 *  Address transtion (physical -> virtual)
 */
static void *
phys__to_virt(void *phys)
{

	return phys_to_virt(phys);
}

/*
 *  Address transtion (virtual -> physical)
 */
static void *
virt__to_phys(void *virt)
{

	return virt_to_phys(virt);
}

/*
 * Initialize device driver module.
 */
void
device_init(void)
{
	struct module *m;
	void (*drv_entry)(const dkifn_t *);

	list_init(&device_list);

	m = &boot_info->driver;
	if (m == NULL)
		return;

	drv_entry = (void (*)(const dkifn_t *))m->entry;
	if (drv_entry == NULL)
		return;
	/*
	 * Call all driver initialization functions.
	 */
	drv_entry(driver_service);
}