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 28 #include <pthread.h> 29 #include <pthread-l4.h> 30 31 #include "notification_client.h" 32 #include "notifier.h" 33 34 35 36 /* Null notification state. */ 37 38 static ObjectNotificationState _null_state; 39 40 41 42 /* Lock protecting per-task notifier access. */ 43 44 static std::mutex _lock; 45 46 /* Per-task storage for specific waiting operations. */ 47 48 static SpecificObjectNotifier *_notifier = NULL; 49 50 51 52 /* Return the per-task notifier for object-specific waiting operations. */ 53 54 SpecificObjectNotifier *notifier_get_task_notifier() 55 { 56 std::lock_guard<std::mutex> guard(_lock); 57 58 /* Start any new notifier. */ 59 60 if (_notifier == NULL) 61 { 62 _notifier = new SpecificObjectNotifier; 63 _notifier->start(); 64 } 65 66 return _notifier; 67 } 68 69 /* Return a local notifier for general object waiting operations. */ 70 71 GeneralObjectNotifier *notifier_get_local_notifier() 72 { 73 GeneralObjectNotifier *notifier = new GeneralObjectNotifier; 74 75 notifier->start(); 76 return notifier; 77 } 78 79 80 81 /* Invoke the mainloop in a thread. */ 82 83 static void *notifier_mainloop(void *data) 84 { 85 ObjectNotifier *notifier = reinterpret_cast<ObjectNotifier *>(data); 86 87 notifier->mainloop(); 88 return 0; 89 } 90 91 92 93 /* Virtual destructor required for base class instance reference deletion. */ 94 95 ObjectNotifier::~ObjectNotifier() 96 { 97 } 98 99 /* Listen for notifications. */ 100 101 void ObjectNotifier::mainloop() 102 { 103 ipc_message_t msg; 104 l4_umword_t label; 105 106 while (1) 107 { 108 ipc_message_wait(&msg, &label); 109 110 /* Clear lower label bits. */ 111 112 label = label & ~3UL; 113 114 /* Ignore erroneous messages. */ 115 116 if (l4_ipc_error(msg.tag, l4_utcb())) 117 continue; 118 119 /* Interpret gate labels as notifiable objects. */ 120 121 notifiable_t *object = (notifiable_t *) label; 122 123 /* Obtain message details. */ 124 125 ipc_message_open(&msg); 126 127 notify_flags_t flags = ipc_message_get_word(&msg, 0); 128 129 /* Reply to notifications. */ 130 131 ipc_message_reply(&msg); 132 ipc_message_discard(&msg); 133 134 /* Register the notification. */ 135 136 _notify(object, flags); 137 } 138 139 ipc_message_free(&msg); 140 } 141 142 /* Start listening for notifications. */ 143 144 long ObjectNotifier::start() 145 { 146 if (_started) 147 return L4_EOK; 148 149 pthread_t thread; 150 pthread_attr_t attr; 151 long err; 152 153 pthread_attr_init(&attr); 154 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); 155 156 err = pthread_create(&thread, &attr, notifier_mainloop, this); 157 if (err) 158 return err; 159 160 _thread = pthread_l4_cap(thread); 161 _started = true; 162 163 return L4_EOK; 164 } 165 166 167 168 /* Return notification state for the given object or null state if no record 169 existed for the object. */ 170 171 ObjectNotificationState &ObjectNotifier::object_state(notifiable_t *object, bool create) 172 { 173 ObjectNotificationStates::iterator it = _state.find(object); 174 175 if (it == _state.end()) 176 { 177 if (create) 178 return _state[object]; 179 else 180 return _null_state; 181 } 182 183 return it->second; 184 } 185 186 /* Subscribe to notification events on an object. */ 187 188 long ObjectNotifier::subscribe(notifiable_t *object, notify_flags_t flags) 189 { 190 /* Acquire the lock for state lookup. */ 191 192 std::unique_lock<std::mutex> state_guard(_state_lock); 193 194 ObjectNotificationState &state = object_state(object, true); 195 long err; 196 197 /* Create a notification endpoint, if necessary. */ 198 199 if (state.is_null()) 200 { 201 err = ipc_server_new_for_thread(&state.endpoint, object, _thread); 202 203 if (err) 204 return err; 205 } 206 207 /* Subscribe, sending the notification endpoint. */ 208 209 client_Notification notify(object->base->ref); 210 211 err = notify.subscribe(state.endpoint, flags); 212 213 if (err) 214 { 215 ipc_cap_free_um(state.endpoint); 216 _state.erase(object); 217 return err; 218 } 219 220 return L4_EOK; 221 } 222 223 /* Unsubscribe from notification events on an object. */ 224 225 long ObjectNotifier::unsubscribe(notifiable_t *object) 226 { 227 /* Acquire the lock for state lookup. */ 228 229 std::unique_lock<std::mutex> state_guard(_state_lock); 230 231 ObjectNotificationState &state = object_state(object, false); 232 233 if (state.is_null()) 234 return -L4_EINVAL; 235 236 /* Unsubscribe via the notification interface. */ 237 238 client_Notification notify(object->base->ref); 239 240 notify.unsubscribe(); 241 242 ipc_cap_free_um(state.endpoint); 243 244 _state.erase(object); 245 246 /* Remove the lock for updating object state. */ 247 248 _object_locks.erase(object); 249 250 return L4_EOK; 251 } 252 253 254 255 /* Handle a notification event for an object. Ideally, this would be invoked by 256 the generic server dispatch mechanism, with the gate label being interpreted 257 and provided as the first parameter. */ 258 259 void GeneralObjectNotifier::_notify(notifiable_t *object, notify_flags_t flags) 260 { 261 /* Enter critical section for the notifier (affecting all objects). */ 262 263 std::unique_lock<std::mutex> general_guard(_general_lock); 264 265 /* Acquire the lock for state lookup. */ 266 267 std::unique_lock<std::mutex> state_guard(_state_lock); 268 269 ObjectNotificationState &state = object_state(object, false); 270 271 if (state.is_null()) 272 return; 273 274 /* Acquire the lock for the object state itself. */ 275 276 std::unique_lock<std::mutex> object_guard(state.lock); 277 278 /* Record flags and return previous flags. */ 279 280 notify_flags_t recorded = state.pending; 281 282 state.pending |= flags; 283 284 /* Add an object queue entry for any objects without previous notifications. */ 285 286 if (!recorded) 287 _affected.push_back(object); 288 289 /* Notify any waiting caller. */ 290 291 _general_condition.notify_one(); 292 } 293 294 void SpecificObjectNotifier::_notify(notifiable_t *object, notify_flags_t flags) 295 { 296 /* Acquire the lock for state lookup. */ 297 298 std::unique_lock<std::mutex> state_guard(_state_lock); 299 300 ObjectNotificationState &state = object_state(object, false); 301 302 if (state.is_null()) 303 return; 304 305 /* Acquire the lock for the object state itself. */ 306 307 std::unique_lock<std::mutex> object_guard(state.lock); 308 309 state.pending |= flags; 310 311 /* Notify any waiting caller. */ 312 313 state.condition.notify_one(); 314 } 315 316 317 318 /* Transfer pending notifications to the given object. This must be called with 319 a lock acquired on the object notification state. */ 320 321 bool ObjectNotifier::_transfer(ObjectNotificationState &state, notifiable_t *object) 322 { 323 notify_flags_t recorded = state.pending; 324 325 if (recorded) 326 { 327 object->notifications = recorded; 328 state.pending = 0; 329 return true; 330 } 331 332 return false; 333 } 334 335 336 337 /* Obtain object state and transfer notifications. */ 338 339 bool GeneralObjectNotifier::_retrieve_for_object(notifiable_t *object) 340 { 341 /* Acquire the lock for state lookup. */ 342 343 std::unique_lock<std::mutex> state_guard(_state_lock); 344 345 ObjectNotificationState &state = object_state(object, false); 346 347 if (state.is_null()) 348 return false; 349 350 /* Acquire the lock for the object state itself, then release the state lock. */ 351 352 std::unique_lock<std::mutex> object_guard(state.lock); 353 354 state_guard.unlock(); 355 356 /* Call generic method to transfer notifications, if possible. */ 357 358 return _transfer(state, object); 359 } 360 361 /* Obtain queued objects until one is found that still has events recorded for 362 it. This must be called with the notifier's general lock acquired. */ 363 364 bool GeneralObjectNotifier::_retrieve(notifiable_t **object) 365 { 366 while (!_affected.empty()) 367 { 368 *object = _affected.front(); 369 _affected.pop_front(); 370 371 if (_retrieve_for_object(*object)) 372 return true; 373 } 374 375 return false; 376 } 377 378 379 380 /* Wait for notification events on objects. */ 381 382 long GeneralObjectNotifier::wait(notifiable_t **object) 383 { 384 std::unique_lock<std::mutex> general_guard(_general_lock); 385 386 while (1) 387 { 388 /* With pending notifications, update the first object and exit. */ 389 390 if (_retrieve(object)) 391 break; 392 393 /* Otherwise, wait for notifications. */ 394 395 _general_condition.wait(general_guard); 396 } 397 398 return L4_EOK; 399 } 400 401 /* Wait for notifications from a single object. */ 402 403 long SpecificObjectNotifier::wait_object(notifiable_t *object) 404 { 405 /* Acquire the lock for reading object state. */ 406 407 std::unique_lock<std::mutex> state_guard(_state_lock); 408 409 ObjectNotificationState &state = object_state(object, false); 410 411 if (state.is_null()) 412 return -L4_EINVAL; 413 414 /* Acquire the lock for the object state itself, then release the state lock. */ 415 416 std::unique_lock<std::mutex> object_guard(state.lock); 417 418 state_guard.unlock(); 419 420 while (1) 421 { 422 /* With pending notifications, update the object and exit. */ 423 424 if (_transfer(state, object)) 425 break; 426 427 /* Otherwise, wait for notifications. */ 428 429 state.condition.wait(object_guard); 430 } 431 432 return L4_EOK; 433 } 434 435 // vim: tabstop=2 expandtab shiftwidth=2