Nathan Skrzypczak | 8acc5ee | 2021-10-12 14:00:25 +0200 | [diff] [blame] | 1 | .. _libmemif_gettingstarted_doc: |
| 2 | |
| 3 | Getting started |
| 4 | =============== |
| 5 | |
| 6 | For detailed information on api calls and structures please refer to |
| 7 | ``libmemif.h``. |
| 8 | |
| 9 | Start by creating a memif socket. Memif socket represents UNIX domain |
| 10 | socket and interfaces assigned to use this socket. Memif uses UNIX domain |
| 11 | socket to communicate with other memif drivers. |
| 12 | |
| 13 | First fill out the ``memif_socket_args`` struct. The minimum required |
| 14 | configuration is the UNIX socket path. > Use ``@`` or ``\0`` at the |
| 15 | beginning of the path to use abstract socket. |
| 16 | |
| 17 | .. code:: c |
| 18 | |
| 19 | memif_socket_args_t sargs; |
| 20 | |
| 21 | strncpy(sargs.path, socket_path, sizeof(sargs.path)); |
| 22 | |
| 23 | .. code:: c |
| 24 | |
| 25 | memif_socket_handle_t memif_socket; |
| 26 | |
| 27 | memif_create_socket(&memif_socket, &sargs, &private_data); |
| 28 | |
| 29 | Once you have created your socket, you can create memif interfaces on |
| 30 | this socket. Fill out the ``memif_conn_args`` struct. Then call |
| 31 | ``memif_create()``. |
| 32 | |
| 33 | .. code:: c |
| 34 | |
| 35 | memif_conn_args_t cargs; |
| 36 | |
| 37 | /* Assign your socket handle */ |
| 38 | cargs.socket = memif_socket; |
| 39 | |
| 40 | .. code:: c |
| 41 | |
| 42 | memif_conn_handle_t conn; |
| 43 | |
| 44 | /* Assign callbacks */ |
| 45 | memif_create (&conn, &cargs, on_connect_cb, on_disconnect_cb, on_interrupt_cb, &private_data); |
| 46 | |
| 47 | Now start the polling events using libmemifs builtin polling. |
| 48 | |
| 49 | .. code:: c |
| 50 | |
| 51 | do { |
| 52 | err = memif_poll_event(memif_socket, /* timeout -1 = blocking */ -1); |
| 53 | } while (err == MEMIF_ERR_SUCCESS); |
| 54 | |
| 55 | Polling can be canceled by calling ``memif_cancel_poll_event()``. |
| 56 | |
| 57 | .. code:: c |
| 58 | |
| 59 | memif_cancel_poll_event (memif_socket); |
| 60 | |
| 61 | On link status change ``on_connect`` and ``on_disconnect`` callbacks are |
| 62 | called respectively. Before you can start transmitting data you, first |
| 63 | need to call ``memif_refill_queue()`` for each RX queue to initialize |
| 64 | this queue. |
| 65 | |
| 66 | .. code:: c |
| 67 | |
| 68 | int on_connect (memif_conn_handle_t conn, void *private_ctx) |
| 69 | { |
| 70 | my_private_data_t *data = (my_private_data_t *) private_ctx; |
| 71 | |
| 72 | err = memif_refill_queue(conn, 0, -1, 0); |
| 73 | if (err != MEMIF_ERR_SUCCESS) { |
| 74 | INFO("memif_refill_queue: %s", memif_strerror(err)); |
| 75 | return err; |
| 76 | } |
| 77 | |
| 78 | /* |
| 79 | * Do stuff. |
| 80 | */ |
| 81 | |
| 82 | return 0; |
| 83 | } |
| 84 | |
| 85 | Now you are ready to transmit packets. > Example implementation |
| 86 | ``examples/common/sender.c`` and ``examples/common/responder.c`` |
| 87 | |
| 88 | To transmit or receive data you will need to use ``memif_buffer`` |
| 89 | struct. The important fields here are ``void *data``, ``uint32_t len`` |
| 90 | and ``uint8_t flags``. The ``data`` pointer points directly to the |
| 91 | shared memory packet buffer. This is where you will find/insert your |
| 92 | packets. The ``len`` field is the length of the buffer. If the flag |
| 93 | ``MEMIF_BUFFER_FLAG_NEXT`` is present in ``flags`` field, this buffer is |
| 94 | chained so the rest of the data is located in the next buffer, and so |
| 95 | on. |
| 96 | |
| 97 | First let’s receive data. To receive data call ``memif_rx_burst()``. The |
| 98 | function will fill out memif buffers passed to it. Then you would |
| 99 | process your data (e.g. copy to your stack). Last you must refill the |
| 100 | queue using ``memif_refill_queue()`` to notify peer that the buffers are |
| 101 | now free and can be overwritten. |
| 102 | |
| 103 | .. code:: c |
| 104 | |
| 105 | /* Fill out memif buffers and mark them as received */ |
| 106 | err = memif_rx_burst(conn, qid, buffers, num_buffers, &num_received); |
| 107 | if (err != MEMIF_ERR_SUCCESS) { |
| 108 | INFO ("memif_rx_burst: %s", memif_strerror(err)); |
| 109 | return err; |
| 110 | } |
| 111 | /* |
| 112 | Process the buffers. |
| 113 | */ |
| 114 | |
| 115 | /* Refill the queue, so that the peer interface can transmit more packets */ |
| 116 | err = memif_refill_queue(conn, qid, num_received, 0); |
| 117 | if (err != MEMIF_ERR_SUCCESS) { |
| 118 | INFO("memif_refill_queue: %s", memif_strerror(err)); |
| 119 | goto error; |
| 120 | } |
| 121 | |
| 122 | In order to transmit data you first need to ‘allocate’ memif buffers |
| 123 | using ``memif_buffer_alloc()``. This function similar to |
| 124 | ``memif_rx_burst`` will fill out provided memif buffers. You will then |
| 125 | insert your packets directly into the shared memory (don’t forget to |
| 126 | update ``len`` filed if your packet is smaller that buffer length). |
| 127 | Finally call ``memif_tx_burst`` to transmit the buffers. |
| 128 | |
| 129 | .. code:: c |
| 130 | |
| 131 | /* Alocate memif buffers */ |
| 132 | err = memif_buffer_alloc(conn, qid, buffers, num_pkts, &num_allocated, packet_size); |
| 133 | if (err != MEMIF_ERR_SUCCESS) { |
| 134 | INFO("memif_buffer_alloc: %s", memif_strerror(err)); |
| 135 | goto error; |
| 136 | } |
| 137 | |
| 138 | /* |
| 139 | Fill out the buffers. |
| 140 | |
| 141 | tx_buffers[i].data field points to the shared memory. |
| 142 | update tx_buffers[i].len to your packet length, if the packet is smaller. |
| 143 | */ |
| 144 | |
| 145 | /* Transmit the buffers */ |
| 146 | err = memif_tx_burst(conn, qid, buffers, num_allocated, &num_transmitted); |
| 147 | if (err != MEMIF_ERR_SUCCESS) { |
| 148 | INFO("memif_tx_burst: %s", memif_strerror(err)); |
| 149 | goto error; |
| 150 | } |
| 151 | |
| 152 | Zero-copy Slave |
| 153 | --------------- |
| 154 | |
| 155 | Interface with slave role is the buffer producer, as such it can use |
| 156 | zero-copy mode. |
| 157 | |
| 158 | After receiving buffers, process your packets in place. Then use |
| 159 | ``memif_buffer_enq_tx()`` to enqueue rx buffers to tx queue (by swapping |
| 160 | rx buffer with a free tx buffer). |
| 161 | |
| 162 | .. code:: c |
| 163 | |
| 164 | /* Fill out memif buffers and mark them as received */ |
| 165 | err = memif_rx_burst(conn, qid, buffers, num_buffers, &num_received); |
| 166 | if (err != MEMIF_ERR_SUCCESS) { |
| 167 | INFO ("memif_rx_burst: %s", memif_strerror(err)); |
| 168 | return err; |
| 169 | } |
| 170 | |
| 171 | /* |
| 172 | Process the buffers in place. |
| 173 | */ |
| 174 | |
| 175 | /* Enqueue processed buffers to tx queue */ |
| 176 | err = memif_buffer_enq_tx(conn, qid, buffers, num_buffers, &num_enqueued); |
| 177 | if (err != MEMIF_ERR_SUCCESS) { |
| 178 | INFO("memif_buffer_alloc: %s", memif_strerror(err)); |
| 179 | goto error; |
| 180 | } |
| 181 | |
| 182 | /* Refill the queue, so that the peer interface can transmit more packets */ |
| 183 | err = memif_refill_queue(conn, qid, num_enqueued, 0); |
| 184 | if (err != MEMIF_ERR_SUCCESS) { |
| 185 | INFO("memif_refill_queue: %s", memif_strerror(err)); |
| 186 | goto error; |
| 187 | } |
| 188 | |
| 189 | /* Transmit the buffers. */ |
| 190 | err = memif_tx_burst(conn, qid, buffers, num_enqueued, &num_transmitted); |
| 191 | if (err != MEMIF_ERR_SUCCESS) { |
| 192 | INFO("memif_tx_burst: %s", memif_strerror(err)); |
| 193 | goto error; |
| 194 | } |
| 195 | |
| 196 | Custom Event Polling |
| 197 | -------------------- |
| 198 | |
| 199 | Libmemif can be integrated into your applications fd event polling. You |
| 200 | will need to implement ``memif_control_fd_update_t`` callback and pass |
| 201 | it to ``memif_socket_args.on_control_fd_update``. Now each time any file |
| 202 | descriptor belonging to that socket updates, ``on_control_fd_update`` |
| 203 | callback is called. The file descriptor and event type is passed in |
| 204 | ``memif_fd_event_t``. It also contains private context that is |
| 205 | associated with this fd. When event is polled on the fd you need to call |
| 206 | ``memif_control_fd_handler`` and pass the event type and private context |
| 207 | associated with the fd. |
| 208 | |
| 209 | Multi Threading |
| 210 | --------------- |
| 211 | |
| 212 | Connection establishment |
| 213 | ~~~~~~~~~~~~~~~~~~~~~~~~ |
| 214 | |
| 215 | Memif sockets should not be handled in parallel. Instead each thread |
| 216 | should have it’s own socket. However the UNIX socket can be the same. In |
| 217 | case of non-listener socket, it’s straight forward, just create the |
| 218 | socket using the same path. In case of listener socket, the polling |
| 219 | should be done by single thread. > The socket becomes listener once a |
| 220 | Master interface is assigned to it. |
| 221 | |
| 222 | Packet handling |
| 223 | ~~~~~~~~~~~~~~~ |
| 224 | |
| 225 | Single queue must not be handled in parallel. Instead you can assign |
| 226 | queues to threads in such way that each queue is only assigned single |
| 227 | thread. |
| 228 | |
| 229 | Shared Memory Layout |
| 230 | -------------------- |
| 231 | |
| 232 | Please refer to `DPDK MEMIF |
| 233 | documentation <http://doc.dpdk.org/guides/nics/memif.html>`__ |
| 234 | ``'Shared memory'`` section. |