/*
 * class.c - basic device class management
 */

#undef DEBUG

#include <linux/device.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/string.h>
#include "base.h"

#define to_class_attr(_attr) container_of(_attr,struct devclass_attribute,attr)
#define to_class(obj) container_of(obj,struct device_class,subsys.kset.kobj)

DECLARE_MUTEX(devclass_sem);

static ssize_t
devclass_attr_show(struct kobject * kobj, struct attribute * attr, char * buf)
{
	struct devclass_attribute * class_attr = to_class_attr(attr);
	struct device_class * dc = to_class(kobj);
	ssize_t ret = 0;

	if (class_attr->show)
		ret = class_attr->show(dc,buf);
	return ret;
}

static ssize_t
devclass_attr_store(struct kobject * kobj, struct attribute * attr, 
		    const char * buf, size_t count)
{
	struct devclass_attribute * class_attr = to_class_attr(attr);
	struct device_class * dc = to_class(kobj);
	ssize_t ret = 0;

	if (class_attr->store)
		ret = class_attr->store(dc,buf,count);
	return ret;
}

static struct sysfs_ops class_sysfs_ops = {
	.show	= devclass_attr_show,
	.store	= devclass_attr_store,
};

static struct kobj_type ktype_devclass = {
	.sysfs_ops	= &class_sysfs_ops,
};

/* Classes can't use the kobject hotplug logic, as
 * they do not add new kobjects to the system */
static decl_subsys(class,&ktype_devclass,NULL);


static int devclass_dev_link(struct device_class * cls, struct device * dev)
{
	char	linkname[16];
	snprintf(linkname,16,"%u",dev->class_num);
	return sysfs_create_link(&cls->devices.kobj,&dev->kobj,linkname);
}

static void devclass_dev_unlink(struct device_class * cls, struct device * dev)
{
	char	linkname[16];
	snprintf(linkname,16,"%u",dev->class_num);
	sysfs_remove_link(&cls->devices.kobj,linkname);
}

static int devclass_drv_link(struct device_driver * drv)
{
	char	name[KOBJ_NAME_LEN * 3];
	snprintf(name,KOBJ_NAME_LEN * 3,"%s:%s",drv->bus->name,drv->name);
	return sysfs_create_link(&drv->devclass->drivers.kobj,&drv->kobj,name);
}

static void devclass_drv_unlink(struct device_driver * drv)
{
	char	name[KOBJ_NAME_LEN * 3];
	snprintf(name,KOBJ_NAME_LEN * 3,"%s:%s",drv->bus->name,drv->name);
	return sysfs_remove_link(&drv->devclass->drivers.kobj,name);
}


int devclass_create_file(struct device_class * cls, struct devclass_attribute * attr)
{
	int error;
	if (cls) {
		error = sysfs_create_file(&cls->subsys.kset.kobj,&attr->attr);
	} else
		error = -EINVAL;
	return error;
}

void devclass_remove_file(struct device_class * cls, struct devclass_attribute * attr)
{
	if (cls)
		sysfs_remove_file(&cls->subsys.kset.kobj,&attr->attr);
}


int devclass_add_driver(struct device_driver * drv)
{
	struct device_class * cls = get_devclass(drv->devclass);
	int error = 0;

	if (cls) {
		down_write(&cls->subsys.rwsem);
		pr_debug("device class %s: adding driver %s:%s\n",
			 cls->name,drv->bus->name,drv->name);
		error = devclass_drv_link(drv);
		
		if (!error)
			list_add_tail(&drv->class_list,&cls->drivers.list);
		up_write(&cls->subsys.rwsem);
	}
	return error;
}

void devclass_remove_driver(struct device_driver * drv)
{
	struct device_class * cls = drv->devclass;
	if (cls) {
		down_write(&cls->subsys.rwsem);
		pr_debug("device class %s: removing driver %s:%s\n",
			 cls->name,drv->bus->name,drv->name);
		list_del_init(&drv->class_list);
		devclass_drv_unlink(drv);
		up_write(&cls->subsys.rwsem);
		put_devclass(cls);
	}
}


static void enum_device(struct device_class * cls, struct device * dev)
{
	u32 val;
	val = cls->devnum++;
	dev->class_num = val;
	devclass_dev_link(cls,dev);
}

static void unenum_device(struct device_class * cls, struct device * dev)
{
	devclass_dev_unlink(cls,dev);
	dev->class_num = 0;
}

/**
 *	devclass_add_device - register device with device class
 *	@dev:   device to be registered 
 *
 *	This is called when a device is either registered with the 
 *	core, or after the a driver module is loaded and bound to
 *	the device. 
 *	The class is determined by looking at @dev's driver, so one
 *	way or another, it must be bound to something. Once the 
 *	class is determined, it's set to prevent against concurrent
 *	calls for the same device stomping on each other. 
 *
 *	/sbin/hotplug should be called once the device is added to 
 *	class and all the interfaces. 
 */
int devclass_add_device(struct device * dev)
{
	struct device_class * cls;
	int error = 0;

	down(&devclass_sem);
	if (dev->driver) {
		cls = get_devclass(dev->driver->devclass);

		if (!cls)
			goto Done;

		pr_debug("device class %s: adding device %s\n",
			 cls->name,dev->name);
		if (cls->add_device) 
			error = cls->add_device(dev);
		if (error) {
			put_devclass(cls);
			goto Done;
		}

		down_write(&cls->subsys.rwsem);
		enum_device(cls,dev);
		list_add_tail(&dev->class_list,&cls->devices.list);
		/* notify userspace (call /sbin/hotplug) */
		class_hotplug (dev, "add");

		up_write(&cls->subsys.rwsem);

		interface_add_dev(dev);
	}
 Done:
	up(&devclass_sem);
	return error;
}

void devclass_remove_device(struct device * dev)
{
	struct device_class * cls;

	down(&devclass_sem);
	if (dev->driver) {
		cls = dev->driver->devclass;
		if (!cls) 
			goto Done;

		interface_remove_dev(dev);

		down_write(&cls->subsys.rwsem);
		pr_debug("device class %s: removing device %s\n",
			 cls->name,dev->name);

		unenum_device(cls,dev);

		list_del(&dev->class_list);

		/* notify userspace (call /sbin/hotplug) */
		class_hotplug (dev, "remove");

		up_write(&cls->subsys.rwsem);

		if (cls->remove_device)
			cls->remove_device(dev);
		put_devclass(cls);
	}
 Done:
	up(&devclass_sem);
}

struct device_class * get_devclass(struct device_class * cls)
{
	return cls ? container_of(subsys_get(&cls->subsys),struct device_class,subsys) : NULL;
}

void put_devclass(struct device_class * cls)
{
	subsys_put(&cls->subsys);
}


int devclass_register(struct device_class * cls)
{
	pr_debug("device class '%s': registering\n",cls->name);
	strncpy(cls->subsys.kset.kobj.name,cls->name,KOBJ_NAME_LEN);
	subsys_set_kset(cls,class_subsys);
	subsystem_register(&cls->subsys);

	snprintf(cls->devices.kobj.name,KOBJ_NAME_LEN,"devices");
	cls->devices.subsys = &cls->subsys;
	kset_register(&cls->devices);

	snprintf(cls->drivers.kobj.name,KOBJ_NAME_LEN,"drivers");
	cls->drivers.subsys = &cls->subsys;
	kset_register(&cls->drivers);

	return 0;
}

void devclass_unregister(struct device_class * cls)
{
	pr_debug("device class '%s': unregistering\n",cls->name);
	kset_unregister(&cls->drivers);
	kset_unregister(&cls->devices);
	subsystem_unregister(&cls->subsys);
}

int __init classes_init(void)
{
	return subsystem_register(&class_subsys);
}

EXPORT_SYMBOL(devclass_create_file);
EXPORT_SYMBOL(devclass_remove_file);
EXPORT_SYMBOL(devclass_register);
EXPORT_SYMBOL(devclass_unregister);
EXPORT_SYMBOL(get_devclass);
EXPORT_SYMBOL(put_devclass);