1 /* 2 * Server binding/registration. 3 * 4 * Copyright (C) 2018, 2019, 2020, 2021, 2022 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 <l4/re/env.h> 23 #include <l4/sys/factory.h> 24 #include <l4/sys/kernel_object.h> 25 #include <l4/sys/rcv_endpoint.h> 26 #include <l4/sys/thread.h> 27 #include <l4/util/util.h> 28 29 #include <stdlib.h> 30 31 #include "cap_alloc.h" 32 #include "irq.h" 33 #include "server.h" 34 35 36 37 /* Associate an IRQ with the main thread. */ 38 39 long ipc_server_apply_irq(l4_cap_idx_t *irq) 40 { 41 return ipc_server_apply_irq_for_thread(irq, l4re_env()->main_thread); 42 } 43 44 /* Associate an IRQ with the given thread. */ 45 46 long ipc_server_apply_irq_for_thread(l4_cap_idx_t *irq, l4_cap_idx_t thread) 47 { 48 /* Create an IRQ for the gate. */ 49 50 long err = ipc_create_irq(irq); 51 52 if (err) 53 return err; 54 55 /* Bind the IRQ to the thread, presenting the label if it provides the 56 incoming message. */ 57 58 err = ipc_bind_irq(*irq, (l4_umword_t) *irq, thread); 59 60 if (err) 61 { 62 ipc_cap_free_um(*irq); 63 return err; 64 } 65 66 return L4_EOK; 67 } 68 69 /* Associate the given IRQ with gate deletion notifications, also decreasing the 70 IPC gate reference count so that such a notification will be delivered when 71 the final client of the IPC gate releases its reference to it. */ 72 73 long ipc_server_apply_deletion_for_thread(l4_cap_idx_t cap, l4_cap_idx_t irq, l4_cap_idx_t thread) 74 { 75 /* Register for notifications. The thread associated with the gate seems to be 76 needed. */ 77 78 long err = l4_error(l4_thread_register_del_irq(thread, irq)); 79 80 if (err) 81 { 82 ipc_cap_free_um(irq); 83 return err; 84 } 85 86 /* Decrease the reference count on the server capability (IPC gate). */ 87 88 return l4_error(l4_kobject_dec_refcnt(cap, 1)); 89 } 90 91 92 93 /* Bind the main thread to the named capability, employing an identifier for 94 the IPC gate and returning the capability index. */ 95 96 long ipc_server_bind(const char *name, l4_umword_t id, l4_cap_idx_t *server) 97 { 98 /* Obtain a reference to the device. */ 99 100 *server = l4re_env_get_cap(name); 101 102 if (l4_is_invalid_cap(*server)) 103 return -L4_ENOENT; 104 105 /* Bind to an IPC gate with the label identifying the server as message 106 destination. */ 107 108 return l4_error(l4_rcv_ep_bind_thread(*server, l4re_env()->main_thread, id)); 109 } 110 111 /* Initialise a new server capability, binding it to the main thread. */ 112 113 long ipc_server_new(l4_cap_idx_t *cap, void *obj) 114 { 115 return ipc_server_new_for_thread(cap, obj, l4re_env()->main_thread); 116 } 117 118 /* Initialise a new server capability, binding it to the given thread. */ 119 120 long ipc_server_new_for_thread(l4_cap_idx_t *cap, void *obj, l4_cap_idx_t thread) 121 { 122 l4_umword_t label = (l4_umword_t) obj; 123 124 /* Create an IPC gate, presenting the resource's label if this gate provides 125 the message. */ 126 127 return ipc_server_new_gate_for_thread(cap, thread, label); 128 } 129 130 /* Create an IPC gate, exporting via a new capability an object with the given 131 identifier. */ 132 133 long ipc_server_new_gate(l4_cap_idx_t *ref, l4_umword_t id) 134 { 135 return ipc_server_new_gate_for_thread(ref, l4re_env()->main_thread, id); 136 } 137 138 /* Create an IPC gate associated with a specific thread. */ 139 140 long ipc_server_new_gate_for_thread(l4_cap_idx_t *ref, l4_cap_idx_t thread, l4_umword_t id) 141 { 142 long err; 143 144 *ref = ipc_cap_alloc(); 145 146 if (l4_is_invalid_cap(*ref)) 147 return -L4_ENOENT; 148 149 /* Associate the object with an IPC gate. */ 150 151 err = l4_error(l4_factory_create_gate(l4re_env()->factory, *ref, thread, id)); 152 153 if (err) 154 ipc_cap_free_um(*ref); 155 156 return err; 157 } 158 159 160 161 /* Support for the convenience macro starting a server for a given object. */ 162 163 long _ipc_server_loop_for(ipc_server_default_config_type *default_config, 164 void *handler_obj, 165 const char *name) 166 { 167 ipc_server_config_type config; 168 long err; 169 170 if (name != NULL) 171 { 172 err = ipc_server_bind(name, (l4_umword_t) &config, &config.server); 173 if (err) 174 return err; 175 } 176 177 _ipc_server_init_for(&config, default_config, handler_obj); 178 179 return ipc_server_start_config(&config); 180 } 181 182 /* Initialise a server for a given object. */ 183 184 long _ipc_server_init_for(ipc_server_config_type *config, 185 ipc_server_default_config_type *default_config, 186 void *handler_obj) 187 { 188 ipc_server_init_config(config); 189 190 config->expected_items = default_config->expected_items; 191 config->handler = default_config->handler; 192 config->handler_obj = handler_obj; 193 194 return L4_EOK; 195 } 196 197 /* Associate a new configuration with an existing server endpoint. */ 198 199 long _ipc_server_add_config(ipc_server_config_type *config, 200 ipc_server_default_config_type *default_config, 201 void *handler_obj, 202 l4_cap_idx_t thread) 203 { 204 _ipc_server_init_for(config, default_config, handler_obj); 205 return ipc_server_start_config_thread(config, thread); 206 } 207 208 /* Complete initialisation of a server in the given thread. */ 209 210 long ipc_server_start_config_thread(ipc_server_config_type *config, 211 l4_cap_idx_t thread) 212 { 213 config->thread = thread; 214 config->config_thread = 1; 215 216 return ipc_server_prepare_config(config); 217 } 218 219 220 221 /* A server main loop handling endpoint deletion for an IPC gate dedicated to 222 a particular object within its own thread. */ 223 224 long ipc_server_managed_loop(ipc_server_config_type *config) 225 { 226 ipc_message_t msg; 227 l4_umword_t label, irq_label; 228 229 /* Permit other endpoints by dynamically interpreting the label. */ 230 231 ipc_server_config_type *config_from_label; 232 233 /* Declare the extent to which capabilities are expected in messages. */ 234 235 long err = ipc_message_expect(&msg, config->expected_items); 236 237 if (err) 238 return err; 239 240 /* Wait for an incoming message. */ 241 242 while (1) 243 { 244 ipc_message_wait(&msg, &label); 245 246 /* Clear lower label bits. */ 247 248 label = label & ~3UL; 249 250 /* Ignore erroneous messages. */ 251 252 if (l4_ipc_error(msg.tag, l4_utcb())) 253 continue; 254 255 /* Message involves the IPC gate itself. */ 256 257 irq_label = (l4_umword_t) config->irq; 258 259 if (label != irq_label) 260 { 261 config_from_label = (ipc_server_config_type *) label; 262 config_from_label->handler(&msg, config_from_label->handler_obj); 263 } 264 265 /* Message involves the IRQ or a termination condition occurred. */ 266 267 else if ((label == irq_label) || msg.terminating) 268 break; 269 } 270 271 /* Free expected capabilities. */ 272 273 ipc_message_free(&msg); 274 275 return L4_EOK; 276 } 277 278 /* A pthread-compatible mainloop initiation function. */ 279 280 void *ipc_server_start_mainloop(void *data) 281 { 282 ipc_server_config_type *config = (ipc_server_config_type *) data; 283 284 long err = ipc_server_managed_loop(config); 285 286 if (config->finaliser != NULL) 287 config->finaliser(config); 288 289 return (void *) err; 290 } 291 292 /* Wait for an incoming message via an IPC gate dedicated to a particular 293 object within its own thread. */ 294 295 l4_msgtag_t ipc_server_wait(l4_umword_t id) 296 { 297 l4_umword_t label; 298 l4_msgtag_t tag; 299 300 /* Ignore errors and messages not intended for the identified gate. */ 301 302 do tag = l4_ipc_wait(l4_utcb(), &label, L4_IPC_NEVER); 303 while (l4_ipc_error(tag, l4_utcb()) || (label & ~3UL) != id); 304 305 return tag; 306 } 307 308 309 310 /* Finalise a server configuration. */ 311 312 void ipc_server_finalise_config(ipc_server_config_type *config) 313 { 314 if (l4_is_valid_cap(config->server)) 315 { 316 ipc_cap_free_um(config->server); 317 config->server = L4_INVALID_CAP; 318 } 319 320 if (l4_is_valid_cap(config->irq)) 321 { 322 ipc_cap_free_um(config->irq); 323 config->irq = L4_INVALID_CAP; 324 } 325 } 326 327 /* Discard any uninitiated thread. */ 328 329 void ipc_server_discard_thread(ipc_server_config_type *config) 330 { 331 if (config->config_thread && l4_is_valid_cap(config->thread)) 332 { 333 ipc_cap_free_um(config->thread); 334 config->thread = L4_INVALID_CAP; 335 } 336 } 337 338 /* Initialise a server configuration. */ 339 340 void ipc_server_init_config(ipc_server_config_type *config) 341 { 342 /* No object references defined. */ 343 344 config->handler_obj = NULL; 345 config->finaliser_obj = NULL; 346 347 /* Expect no capability items by default. */ 348 349 config->expected_items = 0; 350 351 /* No handler or finaliser defined. */ 352 353 config->handler = NULL; 354 config->finaliser = NULL; 355 356 /* No separate thread for the main loop. */ 357 358 config->config_thread = 0; 359 360 /* Main thread by default with IPC gate to be allocated. */ 361 362 config->thread = l4re_env()->main_thread; 363 config->server = L4_INVALID_CAP; 364 365 /* No notifications and with IRQ to be potentially allocated. Auto-deletion is 366 also disabled by default. */ 367 368 config->notifications = 0; 369 config->irq = L4_INVALID_CAP; 370 config->auto_deletion = 0; 371 } 372 373 /* Initialise but do not start a server using the given configuration. */ 374 375 long ipc_server_prepare_config(ipc_server_config_type *config) 376 { 377 long err; 378 379 /* Allocate a new IPC gate if one is not already defined. */ 380 381 if (l4_is_invalid_cap(config->server)) 382 { 383 err = ipc_server_new_for_thread(&config->server, config, config->thread); 384 385 if (err) 386 return err; 387 } 388 389 /* Allocate an IRQ for notifications if requested. */ 390 391 if (config->notifications) 392 { 393 err = ipc_server_apply_irq_for_thread(&config->irq, config->thread); 394 395 if (err) 396 return err; 397 398 /* Apply auto-deletion if appropriate. */ 399 400 if (config->auto_deletion) 401 { 402 err = ipc_server_apply_deletion_for_thread(config->server, config->irq, config->thread); 403 404 if (err) 405 return err; 406 } 407 408 /* Unmask the interrupt. */ 409 410 ipc_init_irq(config->irq); 411 } 412 413 return L4_EOK; 414 } 415 416 /* Initialise and start a server using the given configuration. */ 417 418 long ipc_server_start_config(ipc_server_config_type *config) 419 { 420 long err = ipc_server_prepare_config(config); 421 422 if (err) 423 return err; 424 425 /* With a separate thread, return the last status value. Otherwise, invoke the 426 main loop. */ 427 428 if (config->config_thread) 429 return L4_EOK; 430 else 431 return (long) ipc_server_start_mainloop(config); 432 } 433 434 /* vim: tabstop=2 expandtab shiftwidth=2 435 */