/*	$OpenBSD: kern_sensors.c,v 1.19 2007/06/04 18:42:05 deraadt Exp $	*/

/*
 * Copyright (c) 2005 David Gwynne <dlg@openbsd.org>
 * Copyright (c) 2006 Constantine A. Murenin <cnst+openbsd@bugmail.mojo.ru>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/kthread.h>
#include <sys/queue.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/device.h>
#include <sys/hotplug.h>

#include <sys/sensors.h>
#include "hotplug.h"

int			sensordev_count = 0;
SLIST_HEAD(, ksensordev) sensordev_list = SLIST_HEAD_INITIALIZER(sensordev_list);

struct sensor_task {
	void				*arg;
	void				(*func)(void *);

	int				period;
	time_t				nextrun;
	volatile int			running;
	TAILQ_ENTRY(sensor_task)	entry;
};

void	sensor_task_create(void *);
void	sensor_task_thread(void *);
void	sensor_task_schedule(struct sensor_task *);

TAILQ_HEAD(, sensor_task) tasklist = TAILQ_HEAD_INITIALIZER(tasklist);

void
sensordev_install(struct ksensordev *sensdev)
{
	struct ksensordev *v, *nv;
	int s;

	s = splhigh();
	if (sensordev_count == 0) {
		sensdev->num = 0;
		SLIST_INSERT_HEAD(&sensordev_list, sensdev, list);
	} else {
		for (v = SLIST_FIRST(&sensordev_list);
		    (nv = SLIST_NEXT(v, list)) != NULL; v = nv)
			if (nv->num - v->num > 1)
				break;
		sensdev->num = v->num + 1;
		SLIST_INSERT_AFTER(v, sensdev, list);
	}
	sensordev_count++;
	splx(s);

#if NHOTPLUG > 0
	hotplug_device_attach(DV_DULL, "sensordev");
#endif
}

void
sensor_attach(struct ksensordev *sensdev, struct ksensor *sens)
{
	struct ksensor *v, *nv;
	struct ksensors_head *sh;
	int s, i;

	s = splhigh();
	sh = &sensdev->sensors_list;
	if (sensdev->sensors_count == 0) {
		for (i = 0; i < SENSOR_MAX_TYPES; i++)
			sensdev->maxnumt[i] = 0;
		sens->numt = 0;
		SLIST_INSERT_HEAD(sh, sens, list);
	} else {
		for (v = SLIST_FIRST(sh);
		    (nv = SLIST_NEXT(v, list)) != NULL; v = nv)
			if (v->type == sens->type && (v->type != nv->type || 
			    (v->type == nv->type && nv->numt - v->numt > 1)))
				break;
		/* sensors of the same type go after each other */
		if (v->type == sens->type)
			sens->numt = v->numt + 1;
		else
			sens->numt = 0;
		SLIST_INSERT_AFTER(v, sens, list);
	}
	/* we only increment maxnumt[] if the sensor was added
	 * to the last position of sensors of this type
	 */
	if (sensdev->maxnumt[sens->type] == sens->numt)
		sensdev->maxnumt[sens->type]++;
	sensdev->sensors_count++;
	splx(s);
}

void
sensordev_deinstall(struct ksensordev *sensdev)
{
	int s;

	s = splhigh();
	sensordev_count--;
	SLIST_REMOVE(&sensordev_list, sensdev, ksensordev, list);
	splx(s);

#if NHOTPLUG > 0
	hotplug_device_detach(DV_DULL, "sensordev");
#endif
}

void
sensor_detach(struct ksensordev *sensdev, struct ksensor *sens)
{
	struct ksensors_head *sh;
	int s;

	s = splhigh();
	sh = &sensdev->sensors_list;
	sensdev->sensors_count--;
	SLIST_REMOVE(sh, sens, ksensor, list);
	/* we only decrement maxnumt[] if this is the tail 
	 * sensor of this type
	 */
	if (sens->numt == sensdev->maxnumt[sens->type] - 1)
		sensdev->maxnumt[sens->type]--;
	splx(s);
}

struct ksensordev *
sensordev_get(int num)
{
	struct ksensordev *sd;

	SLIST_FOREACH(sd, &sensordev_list, list)
		if (sd->num == num)
			return (sd);

	return (NULL);
}

struct ksensor *
sensor_find(int dev, enum sensor_type type, int numt)
{
	struct ksensor *s;
	struct ksensordev *sensdev;
	struct ksensors_head *sh;

	sensdev = sensordev_get(dev);
	if (sensdev == NULL)
		return (NULL);

	sh = &sensdev->sensors_list;
	SLIST_FOREACH(s, sh, list)
		if (s->type == type && s->numt == numt)
			return (s);

	return (NULL);
}

int
sensor_task_register(void *arg, void (*func)(void *), int period)
{
	struct sensor_task	*st;

	st = malloc(sizeof(struct sensor_task), M_DEVBUF, M_NOWAIT);
	if (st == NULL)
		return (1);

	st->arg = arg;
	st->func = func;
	st->period = period;

	st->running = 1;

	if (TAILQ_EMPTY(&tasklist))
		kthread_create_deferred(sensor_task_create, NULL);

	st->nextrun = 0;
	TAILQ_INSERT_HEAD(&tasklist, st, entry);
	wakeup(&tasklist);

	return (0);
}

void
sensor_task_unregister(void *arg)
{
	struct sensor_task	*st;

	TAILQ_FOREACH(st, &tasklist, entry) {
		if (st->arg == arg)
			st->running = 0;
	}
}

void
sensor_task_create(void *arg)
{
	if (kthread_create(sensor_task_thread, NULL, NULL, "sensors") != 0)
		panic("sensors kthread");
}

void
sensor_task_thread(void *arg)
{
	struct sensor_task	*st, *nst;
	time_t			now;

	while (!TAILQ_EMPTY(&tasklist)) {
		while ((nst = TAILQ_FIRST(&tasklist))->nextrun >
		    (now = time_uptime))
			tsleep(&tasklist, PWAIT, "timeout",
			    (nst->nextrun - now) * hz);

		while ((st = nst) != NULL) {
			nst = TAILQ_NEXT(st, entry);

			if (st->nextrun > now)
				break;

			/* take it out while we work on it */
			TAILQ_REMOVE(&tasklist, st, entry);

			if (!st->running) {
				free(st, M_DEVBUF);
				continue;
			}

			/* run the task */
			st->func(st->arg);
			/* stick it back in the tasklist */
			sensor_task_schedule(st);
		}
	}

	kthread_exit(0);
}

void
sensor_task_schedule(struct sensor_task *st)
{
	struct sensor_task 	*cst;

	st->nextrun = time_uptime + st->period;

	TAILQ_FOREACH(cst, &tasklist, entry) {
		if (cst->nextrun > st->nextrun) {
			TAILQ_INSERT_BEFORE(cst, st, entry);
			return;
		}
	}

	/* must be an empty list, or at the end of the list */
	TAILQ_INSERT_TAIL(&tasklist, st, entry);
}