1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Greybus operations |
4 | * |
5 | * Copyright 2015-2016 Google Inc. |
6 | */ |
7 | |
8 | #include <linux/string.h> |
9 | #include <linux/sysfs.h> |
10 | #include <linux/module.h> |
11 | #include <linux/init.h> |
12 | #include <linux/spinlock.h> |
13 | #include <linux/idr.h> |
14 | |
15 | #include "audio_manager.h" |
16 | #include "audio_manager_private.h" |
17 | |
18 | static struct kset *manager_kset; |
19 | |
20 | static LIST_HEAD(modules_list); |
21 | static DECLARE_RWSEM(modules_rwsem); |
22 | static DEFINE_IDA(module_id); |
23 | |
24 | /* helpers */ |
25 | static struct gb_audio_manager_module *gb_audio_manager_get_locked(int id) |
26 | { |
27 | struct gb_audio_manager_module *module; |
28 | |
29 | if (id < 0) |
30 | return NULL; |
31 | |
32 | list_for_each_entry(module, &modules_list, list) { |
33 | if (module->id == id) |
34 | return module; |
35 | } |
36 | |
37 | return NULL; |
38 | } |
39 | |
40 | /* public API */ |
41 | int gb_audio_manager_add(struct gb_audio_manager_module_descriptor *desc) |
42 | { |
43 | struct gb_audio_manager_module *module; |
44 | int id; |
45 | int err; |
46 | |
47 | id = ida_alloc(ida: &module_id, GFP_KERNEL); |
48 | if (id < 0) |
49 | return id; |
50 | |
51 | err = gb_audio_manager_module_create(module: &module, manager_kset, |
52 | id, desc); |
53 | if (err) { |
54 | ida_free(&module_id, id); |
55 | return err; |
56 | } |
57 | |
58 | /* Add it to the list */ |
59 | down_write(sem: &modules_rwsem); |
60 | list_add_tail(new: &module->list, head: &modules_list); |
61 | up_write(sem: &modules_rwsem); |
62 | |
63 | return module->id; |
64 | } |
65 | EXPORT_SYMBOL_GPL(gb_audio_manager_add); |
66 | |
67 | int gb_audio_manager_remove(int id) |
68 | { |
69 | struct gb_audio_manager_module *module; |
70 | |
71 | down_write(sem: &modules_rwsem); |
72 | |
73 | module = gb_audio_manager_get_locked(id); |
74 | if (!module) { |
75 | up_write(sem: &modules_rwsem); |
76 | return -EINVAL; |
77 | } |
78 | list_del(entry: &module->list); |
79 | kobject_put(kobj: &module->kobj); |
80 | up_write(sem: &modules_rwsem); |
81 | ida_free(&module_id, id); |
82 | return 0; |
83 | } |
84 | EXPORT_SYMBOL_GPL(gb_audio_manager_remove); |
85 | |
86 | void gb_audio_manager_remove_all(void) |
87 | { |
88 | struct gb_audio_manager_module *module, *next; |
89 | int is_empty; |
90 | |
91 | down_write(sem: &modules_rwsem); |
92 | |
93 | list_for_each_entry_safe(module, next, &modules_list, list) { |
94 | list_del(entry: &module->list); |
95 | ida_free(&module_id, id: module->id); |
96 | kobject_put(kobj: &module->kobj); |
97 | } |
98 | |
99 | is_empty = list_empty(head: &modules_list); |
100 | |
101 | up_write(sem: &modules_rwsem); |
102 | |
103 | if (!is_empty) |
104 | pr_warn("Not all nodes were deleted\n" ); |
105 | } |
106 | EXPORT_SYMBOL_GPL(gb_audio_manager_remove_all); |
107 | |
108 | struct gb_audio_manager_module *gb_audio_manager_get_module(int id) |
109 | { |
110 | struct gb_audio_manager_module *module; |
111 | |
112 | down_read(sem: &modules_rwsem); |
113 | module = gb_audio_manager_get_locked(id); |
114 | kobject_get(kobj: &module->kobj); |
115 | up_read(sem: &modules_rwsem); |
116 | return module; |
117 | } |
118 | EXPORT_SYMBOL_GPL(gb_audio_manager_get_module); |
119 | |
120 | void gb_audio_manager_put_module(struct gb_audio_manager_module *module) |
121 | { |
122 | kobject_put(kobj: &module->kobj); |
123 | } |
124 | EXPORT_SYMBOL_GPL(gb_audio_manager_put_module); |
125 | |
126 | int gb_audio_manager_dump_module(int id) |
127 | { |
128 | struct gb_audio_manager_module *module; |
129 | |
130 | down_read(sem: &modules_rwsem); |
131 | module = gb_audio_manager_get_locked(id); |
132 | up_read(sem: &modules_rwsem); |
133 | |
134 | if (!module) |
135 | return -EINVAL; |
136 | |
137 | gb_audio_manager_module_dump(module); |
138 | return 0; |
139 | } |
140 | EXPORT_SYMBOL_GPL(gb_audio_manager_dump_module); |
141 | |
142 | void gb_audio_manager_dump_all(void) |
143 | { |
144 | struct gb_audio_manager_module *module; |
145 | int count = 0; |
146 | |
147 | down_read(sem: &modules_rwsem); |
148 | list_for_each_entry(module, &modules_list, list) { |
149 | gb_audio_manager_module_dump(module); |
150 | count++; |
151 | } |
152 | up_read(sem: &modules_rwsem); |
153 | |
154 | pr_info("Number of connected modules: %d\n" , count); |
155 | } |
156 | EXPORT_SYMBOL_GPL(gb_audio_manager_dump_all); |
157 | |
158 | /* |
159 | * module init/deinit |
160 | */ |
161 | static int __init manager_init(void) |
162 | { |
163 | manager_kset = kset_create_and_add(GB_AUDIO_MANAGER_NAME, NULL, |
164 | parent_kobj: kernel_kobj); |
165 | if (!manager_kset) |
166 | return -ENOMEM; |
167 | |
168 | #ifdef GB_AUDIO_MANAGER_SYSFS |
169 | gb_audio_manager_sysfs_init(&manager_kset->kobj); |
170 | #endif |
171 | |
172 | return 0; |
173 | } |
174 | |
175 | static void __exit manager_exit(void) |
176 | { |
177 | gb_audio_manager_remove_all(); |
178 | kset_unregister(kset: manager_kset); |
179 | ida_destroy(ida: &module_id); |
180 | } |
181 | |
182 | module_init(manager_init); |
183 | module_exit(manager_exit); |
184 | |
185 | MODULE_LICENSE("GPL" ); |
186 | MODULE_AUTHOR("Svetlin Ankov <ankov_svetlin@projectara.com>" ); |
187 | |