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 <fsserver/resource_server.h> 26 #include <ipc/cap_alloc.h> 27 #include <ipc/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 } 85 86 87 88 /* Start listening for notifications. */ 89 90 long ObjectNotifier::start() 91 { 92 if (_started) 93 return L4_EOK; 94 95 /* Create a new thread to serve a "null" resource. This resource is not used 96 for notifications but merely for control purposes. */ 97 98 NotifierResource *notifier = new NotifierResource; 99 ResourceServer server(notifier); 100 long err = server.start_thread(false); 101 102 if (err) 103 return err; 104 105 _config = server.config(); 106 _started = true; 107 108 return L4_EOK; 109 } 110 111 112 113 /* Return notification state for the given object or null state if no record 114 existed for the object. */ 115 116 ObjectNotificationState &ObjectNotifier::object_state(notifiable_t *object, bool create) 117 { 118 ObjectNotificationStates::iterator it = _state.find(object); 119 120 if (it == _state.end()) 121 { 122 if (create) 123 return _state[object]; 124 else 125 return _null_state; 126 } 127 128 return it->second; 129 } 130 131 /* Subscribe to notification events on an object. */ 132 133 long ObjectNotifier::subscribe(notifiable_t *object, notify_flags_t flags) 134 { 135 /* Acquire the lock for state lookup. */ 136 137 std::unique_lock<std::mutex> state_guard(_state_lock); 138 139 /* Obtain potentially new state for the object. */ 140 141 ObjectNotificationState &state = object_state(object, true); 142 143 if (state.is_null()) 144 { 145 /* Serve the new object in the notifier thread. */ 146 147 NotifierResource *resource = new NotifierResource(this, object); 148 ResourceServer server(resource); 149 long err = server.start_in_thread(_config->thread, false); 150 151 if (err) 152 return err; 153 154 state.endpoint = server.config()->server; 155 } 156 157 /* Subscribe, sending the notification endpoint via the principal reference 158 for the object. */ 159 160 client_Notification notify(object->base->ref); 161 162 return notify.subscribe(state.endpoint, flags); 163 } 164 165 /* Unsubscribe from notification events on an object. */ 166 167 long ObjectNotifier::unsubscribe(notifiable_t *object) 168 { 169 /* Acquire the lock for state lookup. */ 170 171 std::unique_lock<std::mutex> state_guard(_state_lock); 172 173 ObjectNotificationState &state = object_state(object, false); 174 175 if (state.is_null()) 176 return -L4_ENOENT; 177 178 /* Unsubscribe via the notification interface. */ 179 180 client_Notification notify(object->base->ref); 181 182 notify.unsubscribe(state.endpoint); 183 184 return remove_endpoint(object, state.endpoint); 185 } 186 187 /* Remove a notification endpoint for an object. */ 188 189 long ObjectNotifier::remove_endpoint(notifiable_t *object, l4_cap_idx_t endpoint) 190 { 191 if (l4_is_invalid_cap(endpoint)) 192 return -L4_EINVAL; 193 194 ipc_cap_free_um(endpoint); 195 196 _state.erase(object); 197 198 /* Remove the lock for updating object state. */ 199 200 _object_locks.erase(object); 201 202 return L4_EOK; 203 } 204 205 206 207 /* Handle a notification event for an object. Ideally, this would be invoked by 208 the generic server dispatch mechanism, with the gate label being interpreted 209 and provided as the first parameter. */ 210 211 void GeneralObjectNotifier::notify(notifiable_t *object, notify_flags_t flags, 212 notify_values_t values) 213 { 214 /* Enter critical section for the notifier (affecting all objects). */ 215 216 std::unique_lock<std::mutex> general_guard(_general_lock); 217 218 /* Acquire the lock for state lookup. */ 219 220 std::unique_lock<std::mutex> state_guard(_state_lock); 221 222 ObjectNotificationState &state = object_state(object, false); 223 224 if (state.is_null()) 225 return; 226 227 /* Acquire the lock for the object state itself. */ 228 229 std::unique_lock<std::mutex> object_guard(state.lock); 230 231 /* Record flags and note previous flags. */ 232 233 notify_flags_t recorded = state.pending_notifications; 234 235 state.pending_notifications |= flags; 236 state.pending_values = values; 237 238 /* Add an object queue entry for any objects without previous notifications. */ 239 240 if (!recorded) 241 _affected.push_back(object); 242 243 /* Notify any waiting caller. */ 244 245 _general_condition.notify_one(); 246 } 247 248 void SpecificObjectNotifier::notify(notifiable_t *object, notify_flags_t flags, 249 notify_values_t values) 250 { 251 /* Acquire the lock for state lookup. */ 252 253 std::unique_lock<std::mutex> state_guard(_state_lock); 254 255 ObjectNotificationState &state = object_state(object, false); 256 257 if (state.is_null()) 258 return; 259 260 /* Acquire the lock for the object state itself. */ 261 262 std::unique_lock<std::mutex> object_guard(state.lock); 263 264 state.pending_notifications |= flags; 265 state.pending_values = values; 266 267 /* Notify any waiting caller. */ 268 269 state.condition.notify_one(); 270 } 271 272 273 274 /* Transfer pending notifications to the given object. This must be called with 275 a lock acquired on the object notification state. */ 276 277 bool ObjectNotifier::_transfer(ObjectNotificationState &state, notifiable_t *object) 278 { 279 notify_flags_t recorded = state.pending_notifications; 280 281 if (recorded) 282 { 283 object->notifications = recorded; 284 object->values = state.pending_values; 285 state.pending_notifications = 0; 286 return true; 287 } 288 289 return false; 290 } 291 292 293 294 /* Obtain object state and transfer notifications. */ 295 296 bool GeneralObjectNotifier::_retrieve_for_object(notifiable_t *object) 297 { 298 /* Acquire the lock for state lookup. */ 299 300 std::unique_lock<std::mutex> state_guard(_state_lock); 301 302 ObjectNotificationState &state = object_state(object, false); 303 304 if (state.is_null()) 305 return false; 306 307 /* Acquire the lock for the object state itself, then release the state lock. */ 308 309 std::unique_lock<std::mutex> object_guard(state.lock); 310 311 state_guard.unlock(); 312 313 /* Call generic method to transfer notifications, if possible. */ 314 315 return _transfer(state, object); 316 } 317 318 /* Obtain queued objects until one is found that still has events recorded for 319 it. This must be called with the notifier's general lock acquired. */ 320 321 bool GeneralObjectNotifier::_retrieve(notifiable_t **object) 322 { 323 while (!_affected.empty()) 324 { 325 *object = _affected.front(); 326 _affected.pop_front(); 327 328 if (_retrieve_for_object(*object)) 329 return true; 330 } 331 332 return false; 333 } 334 335 336 337 /* Wait for notification events on objects. */ 338 339 long GeneralObjectNotifier::wait(notifiable_t **object) 340 { 341 std::unique_lock<std::mutex> general_guard(_general_lock); 342 343 while (1) 344 { 345 /* With pending notifications, update the first object and exit. */ 346 347 if (_retrieve(object)) 348 break; 349 350 /* Otherwise, wait for notifications. */ 351 352 _general_condition.wait(general_guard); 353 } 354 355 return L4_EOK; 356 } 357 358 /* Wait for notifications from a single object. */ 359 360 long SpecificObjectNotifier::wait_object(notifiable_t *object) 361 { 362 /* Acquire the lock for reading object state. */ 363 364 std::unique_lock<std::mutex> state_guard(_state_lock); 365 366 ObjectNotificationState &state = object_state(object, false); 367 368 if (state.is_null()) 369 return -L4_EINVAL; 370 371 /* Acquire the lock for the object state itself, then release the state lock. */ 372 373 std::unique_lock<std::mutex> object_guard(state.lock); 374 375 state_guard.unlock(); 376 377 while (1) 378 { 379 /* With pending notifications, update the object and exit. */ 380 381 if (_transfer(state, object)) 382 break; 383 384 /* Otherwise, wait for notifications. */ 385 386 state.condition.wait(object_guard); 387 } 388 389 return L4_EOK; 390 } 391 392 393 394 /* Object-specific resource methods. */ 395 396 ipc_server_default_config_type NotifierResource::config() 397 { 398 return config_Notifier; 399 } 400 401 /* Register a notification received by an object-specific resource. */ 402 403 long NotifierResource::notify(notify_flags_t flags, notify_values_t values) 404 { 405 if (_notifier != NULL) 406 _notifier->notify(_object, flags, values); 407 408 return L4_EOK; 409 } 410 411 // vim: tabstop=2 expandtab shiftwidth=2