Commit 8db00736 authored by Svetlin Ankov's avatar Svetlin Ankov Committed by Greg Kroah-Hartman
Browse files

greybus: audio: Add Audio Manager



This is a simple module that keeps a list of connected GB audio
modules.

Whenever a device is attached, an appropriate uevent is sent to
userspace:

    UDEV  [4941.803215] add      /kernel/gb_audio_manager/0 (gb_audio_manager)
    ACTION=add
    CPORT=99
    DEVICES=0x10
    DEVPATH=/kernel/gb_audio_manager/0
    NAME=naim
    PID=64
    SEQNUM=1828
    SLOT=2
    SUBSYSTEM=gb_audio_manager
    USEC_INITIALIZED=802416
    VID=128

And whenever removed:

    UDEV  [4941.836588] remove   /kernel/gb_audio_manager/0 (gb_audio_manager)
    ACTION=remove
    DEVPATH=/kernel/gb_audio_manager/0
    SEQNUM=1833
    SUBSYSTEM=gb_audio_manager
    USEC_INITIALIZED=835681

The API consists of functions for adding, removing and inspecting
added device module descriptions (struct gb_audio_module):

    int                         gb_audio_manager_add(struct gb_audio_module_descriptor *desc);
    int                         gb_audio_manager_remove(int id);
    int                         gb_audio_manager_remove_all(void);
    struct  gb_audio_module*    gb_audio_manager_get_module(int id);
    void                        gb_audio_manager_put_module(struct gb_audio_module *module);
    int                         gb_audio_manager_dump_module(int id);
    void                        gb_audio_manager_dump_all(void);

Devices can be inspected through sysfs in /sys/kernel/gb_audio_manager/{id}/*

If GB_AUDIO_MANAGER_SYSFS is exported as 'true', managing devices can be done
via the SYSFS as well. For instance:

    echo name=naim slot=2 vid=128 pid=64 cport=99 devices=0x10 > /sys/kernel/gb_audio_manager/add
    echo all > /sys/kernel/gb_audio_manager/dump
    echo 2 > /sys/kernel/gb_audio_manager/dump
    echo 2 > /sys/kernel/gb_audio_manager/remove

Signed-off-by: default avatarSvetlin Ankov <ankov_svetlin@projectara.com>
Signed-off-by: default avatarMark Greer <mgreer@animalcreek.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@google.com>
parent 4dbf5056
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -32,6 +32,8 @@ gb-arche-y := arche-platform.o arche-apb-ctrl.o
gb-audio-codec-y := audio_codec.o
gb-audio-gb-y := audio_gb.o
gb-audio-apbridgea-y := audio_apbridgea.o
gb-audio-manager-y += audio_manager.o
gb-audio-manager-y += audio_manager_module.o
gb-camera-y := camera.o

obj-m += greybus.o
@@ -48,6 +50,7 @@ obj-m += gb-audio-codec.o
obj-m += gb-camera.o
obj-m += gb-audio-gb.o
obj-m += gb-audio-apbridgea.o
obj-m += gb-audio-manager.o

KERNELVER		?= $(shell uname -r)
KERNELDIR 		?= /lib/modules/$(KERNELVER)/build
@@ -89,6 +92,12 @@ ccflags-y := -Wall
# needed for trace events
ccflags-y += -I$(src)

GB_AUDIO_MANAGER_SYSFS ?= true
ifeq ($(GB_AUDIO_MANAGER_SYSFS),true)
gb-audio-manager-y += audio_manager_sysfs.o
ccflags-y += -DGB_AUDIO_MANAGER_SYSFS
endif

all: module

tools::
+184 −0
Original line number Diff line number Diff line
/*
 * Greybus operations
 *
 * Copyright 2015-2016 Google Inc.
 *
 * Released under the GPLv2 only.
 */

#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/rwlock.h>

#include "audio_manager.h"
#include "audio_manager_private.h"

static struct kset *manager_kset;

static LIST_HEAD(modules_list);
static DEFINE_RWLOCK(modules_lock);

static int current_module_id;

/* helpers */
static struct gb_audio_manager_module *gb_audio_manager_get_locked(int id)
{
	struct gb_audio_manager_module *module;

	if (id < 0)
		return NULL;

	list_for_each_entry(module, &modules_list, list) {
		if (module->id == id)
			return module;
	}

	return NULL;
}

/* public API */
int gb_audio_manager_add(struct gb_audio_manager_module_descriptor *desc)
{
	struct gb_audio_manager_module *module;
	unsigned long flags;
	int err;

	err = gb_audio_manager_module_create(&module, manager_kset,
					     current_module_id++, desc);
	if (err)
		return err;

	/* Add it to the list */
	write_lock_irqsave(&modules_lock, flags);
	list_add_tail(&module->list, &modules_list);
	write_unlock_irqrestore(&modules_lock, flags);

	return module->id;
}
EXPORT_SYMBOL_GPL(gb_audio_manager_add);

int gb_audio_manager_remove(int id)
{
	struct gb_audio_manager_module *module;
	unsigned long flags;

	write_lock_irqsave(&modules_lock, flags);

	module = gb_audio_manager_get_locked(id);
	if (!module) {
		write_unlock_irqrestore(&modules_lock, flags);
		return -EINVAL;
	}

	list_del(&module->list);
	kobject_put(&module->kobj);
	write_unlock_irqrestore(&modules_lock, flags);
	return 0;
}
EXPORT_SYMBOL_GPL(gb_audio_manager_remove);

void gb_audio_manager_remove_all(void)
{
	struct gb_audio_manager_module *module, *next;
	int is_empty = 1;
	unsigned long flags;

	write_lock_irqsave(&modules_lock, flags);

	list_for_each_entry_safe(module, next, &modules_list, list) {
		list_del(&module->list);
		kobject_put(&module->kobj);
	}

	is_empty = list_empty(&modules_list);

	write_unlock_irqrestore(&modules_lock, flags);

	if (!is_empty)
		pr_warn("Not all nodes were deleted\n");
}
EXPORT_SYMBOL_GPL(gb_audio_manager_remove_all);

struct gb_audio_manager_module *gb_audio_manager_get_module(int id)
{
	struct gb_audio_manager_module *module;
	unsigned long flags;

	read_lock_irqsave(&modules_lock, flags);
	module = gb_audio_manager_get_locked(id);
	kobject_get(&module->kobj);
	read_unlock_irqrestore(&modules_lock, flags);
	return module;
}
EXPORT_SYMBOL_GPL(gb_audio_manager_get_module);

void gb_audio_manager_put_module(struct gb_audio_manager_module *module)
{
	kobject_put(&module->kobj);
}
EXPORT_SYMBOL_GPL(gb_audio_manager_put_module);

int gb_audio_manager_dump_module(int id)
{
	struct gb_audio_manager_module *module;
	unsigned long flags;

	read_lock_irqsave(&modules_lock, flags);
	module = gb_audio_manager_get_locked(id);
	read_unlock_irqrestore(&modules_lock, flags);

	if (!module)
		return -EINVAL;

	gb_audio_manager_module_dump(module);
	return 0;
}
EXPORT_SYMBOL_GPL(gb_audio_manager_dump_module);

void gb_audio_manager_dump_all(void)
{
	struct gb_audio_manager_module *module;
	int count = 0;
	unsigned long flags;

	read_lock_irqsave(&modules_lock, flags);
	list_for_each_entry(module, &modules_list, list) {
		gb_audio_manager_module_dump(module);
		count++;
	}
	read_unlock_irqrestore(&modules_lock, flags);

	pr_info("Number of connected modules: %d\n", count);
}
EXPORT_SYMBOL_GPL(gb_audio_manager_dump_all);

/*
 * module init/deinit
 */
static int __init manager_init(void)
{
	manager_kset = kset_create_and_add(GB_AUDIO_MANAGER_NAME, NULL,
					   kernel_kobj);
	if (!manager_kset)
		return -ENOMEM;

#ifdef GB_AUDIO_MANAGER_SYSFS
	gb_audio_manager_sysfs_init(&manager_kset->kobj);
#endif

	return 0;
}

static void __exit manager_exit(void)
{
	gb_audio_manager_remove_all();
	kset_unregister(manager_kset);
}

module_init(manager_init);
module_exit(manager_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Svetlin Ankov <ankov_svetlin@projectara.com>");
+82 −0
Original line number Diff line number Diff line
/*
 * Greybus operations
 *
 * Copyright 2015-2016 Google Inc.
 *
 * Released under the GPLv2 only.
 */

#ifndef _GB_AUDIO_MANAGER_H_
#define _GB_AUDIO_MANAGER_H_

#include <linux/kobject.h>
#include <linux/list.h>

#define GB_AUDIO_MANAGER_NAME "gb_audio_manager"
#define GB_AUDIO_MANAGER_MODULE_NAME_LEN 64
#define GB_AUDIO_MANAGER_MODULE_NAME_LEN_SSCANF "63"

struct gb_audio_manager_module_descriptor {
	char name[GB_AUDIO_MANAGER_MODULE_NAME_LEN];
	int slot;
	int vid;
	int pid;
	int cport;
	unsigned int devices;
};

struct gb_audio_manager_module {
	struct kobject kobj;
	struct list_head list;
	int id;
	struct gb_audio_manager_module_descriptor desc;
};

/*
 * Creates a new gb_audio_manager_module_descriptor, using the specified
 * descriptor.
 *
 * Returns a negative result on error, or the id of the newly created module.
 *
 */
int gb_audio_manager_add(struct gb_audio_manager_module_descriptor *desc);

/*
 * Removes a connected gb_audio_manager_module_descriptor for the specified ID.
 *
 * Returns zero on success, or a negative value on error.
 */
int gb_audio_manager_remove(int id);

/*
 * Removes all connected gb_audio_modules
 *
 * Returns zero on success, or a negative value on error.
 */
void gb_audio_manager_remove_all(void);

/*
 * Retrieves a gb_audio_manager_module_descriptor for the specified id.
 * Returns the gb_audio_manager_module_descriptor structure,
 * or NULL if there is no module with the specified ID.
 */
struct gb_audio_manager_module *gb_audio_manager_get_module(int id);

/*
 * Decreases the refcount of the module, obtained by the get function.
 * Modules are removed via gb_audio_manager_remove
 */
void gb_audio_manager_put_module(struct gb_audio_manager_module *module);

/*
 * Dumps the module for the specified id
 * Return 0 on success
 */
int gb_audio_manager_dump_module(int id);

/*
 * Dumps all connected modules
 */
void gb_audio_manager_dump_all(void);

#endif /* _GB_AUDIO_MANAGER_H_ */
+240 −0
Original line number Diff line number Diff line
/*
 * Greybus operations
 *
 * Copyright 2015-2016 Google Inc.
 *
 * Released under the GPLv2 only.
 */

#include <linux/slab.h>

#include "audio_manager.h"
#include "audio_manager_private.h"

#define to_gb_audio_module_attr(x)	\
		container_of(x, struct gb_audio_manager_module_attribute, attr)
#define to_gb_audio_module(x)		\
		container_of(x, struct gb_audio_manager_module, kobj)

struct gb_audio_manager_module_attribute {
	struct attribute attr;
	ssize_t (*show)(struct gb_audio_manager_module *module,
			struct gb_audio_manager_module_attribute *attr,
			char *buf);
	ssize_t (*store)(struct gb_audio_manager_module *module,
			 struct gb_audio_manager_module_attribute *attr,
			 const char *buf, size_t count);
};

static ssize_t gb_audio_module_attr_show(
	struct kobject *kobj, struct attribute *attr, char *buf)
{
	struct gb_audio_manager_module_attribute *attribute;
	struct gb_audio_manager_module *module;

	attribute = to_gb_audio_module_attr(attr);
	module = to_gb_audio_module(kobj);

	if (!attribute->show)
		return -EIO;

	return attribute->show(module, attribute, buf);
}

static ssize_t gb_audio_module_attr_store(struct kobject *kobj,
					  struct attribute *attr,
					  const char *buf, size_t len)
{
	struct gb_audio_manager_module_attribute *attribute;
	struct gb_audio_manager_module *module;

	attribute = to_gb_audio_module_attr(attr);
	module = to_gb_audio_module(kobj);

	if (!attribute->store)
		return -EIO;

	return attribute->store(module, attribute, buf, len);
}

static const struct sysfs_ops gb_audio_module_sysfs_ops = {
	.show = gb_audio_module_attr_show,
	.store = gb_audio_module_attr_store,
};

static void gb_audio_module_release(struct kobject *kobj)
{
	struct gb_audio_manager_module *module = to_gb_audio_module(kobj);

	pr_info("Destroying audio module #%d\n", module->id);
	/* TODO -> delete from list */
	kfree(module);
}

static ssize_t gb_audio_module_name_show(
	struct gb_audio_manager_module *module,
	struct gb_audio_manager_module_attribute *attr, char *buf)
{
	return sprintf(buf, "%s", module->desc.name);
}

static struct gb_audio_manager_module_attribute gb_audio_module_name_attribute =
	__ATTR(name, 0664, gb_audio_module_name_show, NULL);

static ssize_t gb_audio_module_slot_show(
	struct gb_audio_manager_module *module,
	struct gb_audio_manager_module_attribute *attr, char *buf)
{
	return sprintf(buf, "%d", module->desc.slot);
}

static struct gb_audio_manager_module_attribute gb_audio_module_slot_attribute =
	__ATTR(slot, 0664, gb_audio_module_slot_show, NULL);

static ssize_t gb_audio_module_vid_show(
	struct gb_audio_manager_module *module,
	struct gb_audio_manager_module_attribute *attr, char *buf)
{
	return sprintf(buf, "%d", module->desc.vid);
}

static struct gb_audio_manager_module_attribute gb_audio_module_vid_attribute =
	__ATTR(vid, 0664, gb_audio_module_vid_show, NULL);

static ssize_t gb_audio_module_pid_show(
	struct gb_audio_manager_module *module,
	struct gb_audio_manager_module_attribute *attr, char *buf)
{
	return sprintf(buf, "%d", module->desc.pid);
}

static struct gb_audio_manager_module_attribute gb_audio_module_pid_attribute =
	__ATTR(pid, 0664, gb_audio_module_pid_show, NULL);

static ssize_t gb_audio_module_cport_show(
	struct gb_audio_manager_module *module,
	struct gb_audio_manager_module_attribute *attr, char *buf)
{
	return sprintf(buf, "%d", module->desc.cport);
}

static struct gb_audio_manager_module_attribute
					gb_audio_module_cport_attribute =
	__ATTR(cport, 0664, gb_audio_module_cport_show, NULL);

static ssize_t gb_audio_module_devices_show(
	struct gb_audio_manager_module *module,
	struct gb_audio_manager_module_attribute *attr, char *buf)
{
	return sprintf(buf, "0x%X", module->desc.devices);
}

static struct gb_audio_manager_module_attribute
					gb_audio_module_devices_attribute =
	__ATTR(devices, 0664, gb_audio_module_devices_show, NULL);

static struct attribute *gb_audio_module_default_attrs[] = {
	&gb_audio_module_name_attribute.attr,
	&gb_audio_module_slot_attribute.attr,
	&gb_audio_module_vid_attribute.attr,
	&gb_audio_module_pid_attribute.attr,
	&gb_audio_module_cport_attribute.attr,
	&gb_audio_module_devices_attribute.attr,
	NULL,   /* need to NULL terminate the list of attributes */
};

static struct kobj_type gb_audio_module_type = {
	.sysfs_ops = &gb_audio_module_sysfs_ops,
	.release = gb_audio_module_release,
	.default_attrs = gb_audio_module_default_attrs,
};

static void send_add_uevent(struct gb_audio_manager_module *module)
{
	char name_string[128];
	char slot_string[64];
	char vid_string[64];
	char pid_string[64];
	char cport_string[64];
	char devices_string[64];

	char *envp[] = {
		name_string,
		slot_string,
		vid_string,
		pid_string,
		cport_string,
		devices_string,
		NULL
	};

	snprintf(name_string, 128, "NAME=%s", module->desc.name);
	snprintf(slot_string, 64, "SLOT=%d", module->desc.slot);
	snprintf(vid_string, 64, "VID=%d", module->desc.vid);
	snprintf(pid_string, 64, "PID=%d", module->desc.pid);
	snprintf(cport_string, 64, "CPORT=%d", module->desc.cport);
	snprintf(devices_string, 64, "DEVICES=0x%X", module->desc.devices);

	kobject_uevent_env(&module->kobj, KOBJ_ADD, envp);
}

int gb_audio_manager_module_create(
	struct gb_audio_manager_module **module,
	struct kset *manager_kset,
	int id, struct gb_audio_manager_module_descriptor *desc)
{
	int err;
	struct gb_audio_manager_module *m;

	m = kzalloc(sizeof(*m), GFP_ATOMIC);
	if (!m)
		return -ENOMEM;

	/* Initialize the node */
	INIT_LIST_HEAD(&m->list);

	/* Set the module id */
	m->id = id;

	/* Copy the provided descriptor */
	memcpy(&m->desc, desc, sizeof(*desc));

	/* set the kset */
	m->kobj.kset = manager_kset;

	/*
	 * Initialize and add the kobject to the kernel.  All the default files
	 * will be created here.  As we have already specified a kset for this
	 * kobject, we don't have to set a parent for the kobject, the kobject
	 * will be placed beneath that kset automatically.
	 */
	err = kobject_init_and_add(&m->kobj, &gb_audio_module_type, NULL, "%d",
				   id);
	if (err) {
		pr_err("failed initializing kobject for audio module #%d\n",
		       id);
		kobject_put(&m->kobj);
		return err;
	}

	/*
	 * Notify the object was created
	 */
	send_add_uevent(m);

	*module = m;
	pr_info("Created audio module #%d\n", id);
	return 0;
}

void gb_audio_manager_module_dump(struct gb_audio_manager_module *module)
{
	pr_info("audio module #%d name=%s slot=%d vid=%d pid=%d cport=%d devices=0x%X\n",
		module->id,
		module->desc.name,
		module->desc.slot,
		module->desc.vid,
		module->desc.pid,
		module->desc.cport,
		module->desc.devices);
}
+28 −0
Original line number Diff line number Diff line
/*
 * Greybus operations
 *
 * Copyright 2015-2016 Google Inc.
 *
 * Released under the GPLv2 only.
 */

#ifndef _GB_AUDIO_MANAGER_PRIVATE_H_
#define _GB_AUDIO_MANAGER_PRIVATE_H_

#include <linux/kobject.h>

#include "audio_manager.h"

int gb_audio_manager_module_create(
	struct gb_audio_manager_module **module,
	struct kset *manager_kset,
	int id, struct gb_audio_manager_module_descriptor *desc);

/* module destroyed via kobject_put */

void gb_audio_manager_module_dump(struct gb_audio_manager_module *module);

/* sysfs control */
void gb_audio_manager_sysfs_init(struct kobject *kobj);

#endif /* _GB_AUDIO_MANAGER_PRIVATE_H_ */
Loading