1 /* 2 * Generic object event notification support. 3 * 4 * Copyright (C) 2021, 2022, 2023 Paul Boddie <paul@boddie.org.uk> 5 * 6 * This program is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU General Public License as 8 * published by the Free Software Foundation; either version 2 of 9 * the License, or (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program; if not, write to the Free Software 18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 * Boston, MA 02110-1301, USA 20 */ 21 22 #include <map> 23 #include <mutex> 24 25 #include <ipc/cap_alloc.h> 26 #include <ipc/server.h> 27 #include <resource/resource_server.h> 28 29 #include "notification_client.h" 30 #include "notifier.h" 31 #include "notifier_server.h" 32 33 34 35 /* Null notification state. */ 36 37 static ObjectNotificationState _null_state; 38 39 40 41 /* Lock protecting per-task notifier access. */ 42 43 static std::mutex _lock; 44 45 /* Per-task storage for specific waiting operations. */ 46 47 static SpecificObjectNotifier *_notifier = NULL; 48 49 50 51 /* Return the per-task notifier for object-specific waiting operations. */ 52 53 SpecificObjectNotifier *notifier_get_task_notifier() 54 { 55 std::lock_guard<std::mutex> guard(_lock); 56 57 /* Start any new notifier. */ 58 59 if (_notifier == NULL) 60 { 61 _notifier = new SpecificObjectNotifier; 62 _notifier->start(); 63 } 64 65 return _notifier; 66 } 67 68 /* Return a local notifier for general object waiting operations. */ 69 70 GeneralObjectNotifier *notifier_get_local_notifier() 71 { 72 GeneralObjectNotifier *notifier = new GeneralObjectNotifier; 73 74 notifier->start(); 75 return notifier; 76 } 77 78 79 80 /* Virtual destructor required for base class instance reference deletion. */ 81 82 ObjectNotifier::~ObjectNotifier() 83 { 84 ServerConfigs::iterator it; 85 86 for (it = _configs.begin(); it != _configs.end(); it++) 87 delete *it; 88 89 _configs.clear(); 90 } 91 92 93 94 /* Start listening for notifications. */ 95 96 long ObjectNotifier::start() 97 { 98 if (_started) 99 return L4_EOK; 100 101 /* Create a new thread to serve a "null" resource. This resource is not used 102 for notifications but merely for control purposes. */ 103 104 NotifierResource *notifier = new NotifierResource; 105 ResourceServer server(notifier); 106 long err = server.start_thread(false); 107 108 if (err) 109 return err; 110 111 _configs.push_back(server.config()); 112 _started = true; 113 114 return L4_EOK; 115 } 116 117 118 119 /* Return notification state for the given object or null state if no record 120 existed for the object. */ 121 122 ObjectNotificationState &ObjectNotifier::object_state(notifiable_t *object, bool create) 123 { 124 ObjectNotificationStates::iterator it = _state.find(object); 125 126 if (it == _state.end()) 127 { 128 if (create) 129 return _state[object]; 130 else 131 return _null_state; 132 } 133 134 return it->second; 135 } 136 137 /* Subscribe to notification events on an object. */ 138 139 long ObjectNotifier::subscribe(notifiable_t *object, notify_flags_t flags) 140 { 141 /* Acquire the lock for state lookup. */ 142 143 std::unique_lock<std::mutex> state_guard(_state_lock); 144 145 /* Obtain potentially new state for the object. */ 146 147 ObjectNotificationState &state = object_state(object, true); 148 149 if (state.is_null()) 150 { 151 /* Serve the new object in the notifier thread. */ 152 153 NotifierResource *resource = new NotifierResource(this, object); 154 ResourceServer server(resource); 155 long err = server.start_in_thread(_configs.front()->thread, false); 156 157 if (err) 158 return err; 159 160 _configs.push_back(server.config()); 161 state.endpoint = server.config()->server; 162 } 163 164 /* Subscribe, sending the notification endpoint via the principal reference 165 for the object. */ 166 167 client_Notification notify(object->base->ref); 168 169 return notify.subscribe(state.endpoint, flags); 170 } 171 172 /* Unsubscribe from notification events on an object. */ 173 174 long ObjectNotifier::unsubscribe(notifiable_t *object) 175 { 176 /* Acquire the lock for state lookup. */ 177 178 std::unique_lock<std::mutex> state_guard(_state_lock); 179 180 ObjectNotificationState &state = object_state(object, false); 181 182 if (state.is_null()) 183 return -L4_ENOENT; 184 185 /* Unsubscribe via the notification interface. */ 186 187 client_Notification notify(object->base->ref); 188 189 notify.unsubscribe(state.endpoint); 190 191 return remove_endpoint(object, state.endpoint); 192 } 193 194 /* Remove a notification endpoint for an object. */ 195 196 long ObjectNotifier::remove_endpoint(notifiable_t *object, l4_cap_idx_t endpoint) 197 { 198 if (l4_is_invalid_cap(endpoint)) 199 return -L4_EINVAL; 200 201 ipc_cap_free_um(endpoint); 202 203 _state.erase(object); 204 205 /* Remove the lock for updating object state. */ 206 207 _object_locks.erase(object); 208 209 return L4_EOK; 210 } 211 212 213 214 /* Handle a notification event for an object. */ 215 216 void GeneralObjectNotifier::notify(notifiable_t *object, notify_flags_t flags, 217 notify_values_t values) 218 { 219 /* Enter critical section for the notifier (affecting all objects). */ 220 221 std::unique_lock<std::mutex> general_guard(_general_lock); 222 223 /* Acquire the lock for state lookup. */ 224 225 std::unique_lock<std::mutex> state_guard(_state_lock); 226 227 ObjectNotificationState &state = object_state(object, false); 228 229 if (state.is_null()) 230 return; 231 232 /* Acquire the lock for the object state itself. */ 233 234 std::unique_lock<std::mutex> object_guard(state.lock); 235 236 /* Record flags and note previous flags. */ 237 238 notify_flags_t recorded = state.pending_notifications; 239 240 state.pending_notifications |= flags; 241 state.pending_values = values; 242 243 /* Add an object queue entry for any objects without previous notifications. */ 244 245 if (!recorded) 246 _affected.push_back(object); 247 248 /* Notify any waiting caller. */ 249 250 _general_condition.notify_one(); 251 } 252 253 void SpecificObjectNotifier::notify(notifiable_t *object, notify_flags_t flags, 254 notify_values_t values) 255 { 256 /* Acquire the lock for state lookup. */ 257 258 std::unique_lock<std::mutex> state_guard(_state_lock); 259 260 ObjectNotificationState &state = object_state(object, false); 261 262 if (state.is_null()) 263 return; 264 265 /* Acquire the lock for the object state itself. */ 266 267 std::unique_lock<std::mutex> object_guard(state.lock); 268 269 state.pending_notifications |= flags; 270 state.pending_values = values; 271 272 /* Notify any waiting caller. */ 273 274 state.condition.notify_one(); 275 } 276 277 278 279 /* Transfer pending notifications to the given object. This must be called with 280 a lock acquired on the object notification state. */ 281 282 bool ObjectNotifier::_transfer(ObjectNotificationState &state, notifiable_t *object) 283 { 284 notify_flags_t recorded = state.pending_notifications; 285 286 if (recorded) 287 { 288 object->notifications = recorded; 289 object->values = state.pending_values; 290 state.pending_notifications = 0; 291 return true; 292 } 293 294 return false; 295 } 296 297 298 299 /* Obtain object state and transfer notifications. */ 300 301 bool GeneralObjectNotifier::_retrieve_for_object(notifiable_t *object) 302 { 303 /* Acquire the lock for state lookup. */ 304 305 std::unique_lock<std::mutex> state_guard(_state_lock); 306 307 ObjectNotificationState &state = object_state(object, false); 308 309 if (state.is_null()) 310 return false; 311 312 /* Acquire the lock for the object state itself, then release the state lock. */ 313 314 std::unique_lock<std::mutex> object_guard(state.lock); 315 316 state_guard.unlock(); 317 318 /* Call generic method to transfer notifications, if possible. */ 319 320 return _transfer(state, object); 321 } 322 323 /* Obtain queued objects until one is found that still has events recorded for 324 it. This must be called with the notifier's general lock acquired. */ 325 326 bool GeneralObjectNotifier::_retrieve(notifiable_t **object) 327 { 328 while (!_affected.empty()) 329 { 330 *object = _affected.front(); 331 _affected.pop_front(); 332 333 if (_retrieve_for_object(*object)) 334 return true; 335 } 336 337 return false; 338 } 339 340 341 342 /* Wait for notification events on objects. */ 343 344 long GeneralObjectNotifier::wait(notifiable_t **object) 345 { 346 std::unique_lock<std::mutex> general_guard(_general_lock); 347 348 while (1) 349 { 350 /* With pending notifications, update the first object and exit. */ 351 352 if (_retrieve(object)) 353 break; 354 355 /* Otherwise, wait for notifications. */ 356 357 _general_condition.wait(general_guard); 358 } 359 360 return L4_EOK; 361 } 362 363 /* Wait for notifications from a single object. */ 364 365 long SpecificObjectNotifier::wait_object(notifiable_t *object) 366 { 367 /* Acquire the lock for reading object state. */ 368 369 std::unique_lock<std::mutex> state_guard(_state_lock); 370 371 ObjectNotificationState &state = object_state(object, false); 372 373 if (state.is_null()) 374 return -L4_EINVAL; 375 376 /* Acquire the lock for the object state itself, then release the state lock. */ 377 378 std::unique_lock<std::mutex> object_guard(state.lock); 379 380 state_guard.unlock(); 381 382 while (1) 383 { 384 /* With pending notifications, update the object and exit. */ 385 386 if (_transfer(state, object)) 387 break; 388 389 /* Otherwise, wait for notifications. */ 390 391 state.condition.wait(object_guard); 392 } 393 394 return L4_EOK; 395 } 396 397 398 399 /* Object-specific resource methods. */ 400 401 ipc_server_default_config_type NotifierResource::config() 402 { 403 return config_Notifier; 404 } 405 406 /* Register a notification received by an object-specific resource. */ 407 408 long NotifierResource::notify(notify_flags_t flags, notify_values_t values) 409 { 410 if (_notifier != NULL) 411 _notifier->notify(_object, flags, values); 412 413 return L4_EOK; 414 } 415 416 // vim: tabstop=2 expandtab shiftwidth=2