NVIDIA DOCA SDK Data Center on a Chip Framework Documentation
devemu_vfs_device_hotplug_unplug_sample.c
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2023 NVIDIA CORPORATION AND AFFILIATES. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without modification, are permitted
5  * provided that the following conditions are met:
6  * * Redistributions of source code must retain the above copyright notice, this list of
7  * conditions and the following disclaimer.
8  * * Redistributions in binary form must reproduce the above copyright notice, this list of
9  * conditions and the following disclaimer in the documentation and/or other materials
10  * provided with the distribution.
11  * * Neither the name of the NVIDIA CORPORATION nor the names of its contributors may be used
12  * to endorse or promote products derived from this software without specific prior written
13  * permission.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
16  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
17  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
19  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
20  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
21  * STRICT LIABILITY, OR TOR (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
22  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23  *
24  */
25 
26 #include <signal.h>
27 #include <stdbool.h>
28 #include <stdint.h>
29 #include <string.h>
30 #include <time.h>
31 
32 #include <doca_ctx.h>
33 #include <doca_devemu_pci.h>
34 #include <doca_dev.h>
35 #include <doca_error.h>
36 #include <doca_log.h>
37 
38 #include <doca_devemu_vfs_type.h>
39 #include <doca_devemu_vfs.h>
40 
41 DOCA_LOG_REGISTER(DPU_DEVEMU_VFS_DEVICE_CREATE);
42 
43 #define SLEEP_IN_MICROS (10) /* Sample the task every 10 microseconds */
44 #define SLEEP_IN_NANOS (SLEEP_IN_MICROS * 1000)
45 #define TIMEOUT_IN_MICROS (5 * 1000 * 1000) /* Set timeout to 5 seconds */
46 
47 #define VFS_DEVICE_QUEUE_SIZE 64
48 #define VFS_DEVICE_NUM_QUEUES 16
49 
50 static const char tag[DOCA_VFS_TAG_SIZE + 1] = "virtiofs-sample-tag";
51 
52 struct program_state {
53  struct doca_pe *pe;
54  struct doca_dev *dev;
55  struct doca_dev_rep *rep;
56  struct doca_ctx *ctx;
57  struct doca_devemu_vfs_dev *vfs_dev;
58  struct doca_devemu_vfs_type *vfs_type;
59 };
60 
61 static bool unplug_requested; /* Shared variable to allow for a proper shutdown */
62 
63 /*
64  * Signal handler
65  *
66  * @signum [in]: Signal number to handle
67  */
68 static void signal_handler(int signum)
69 {
70  if (signum == SIGINT || signum == SIGTERM) {
71  DOCA_LOG_INFO("Signal %d received, preparing to exit", signum);
72  unplug_requested = true;
73  }
74 }
75 
76 /*
77  * Cleanup state of the sample
78  *
79  * @state [in]: The state of the sample
80  */
81 static void state_cleanup(struct program_state *state)
82 {
83  doca_error_t res;
84 
85  if (state->ctx != NULL) {
87  if (res != DOCA_SUCCESS)
88  DOCA_LOG_ERR("Failed to stop DOCA Emulated Device context: %s", doca_error_get_descr(res));
89  }
90 
91  if (state->vfs_type != NULL && state->vfs_dev != NULL) {
93  if (res != DOCA_SUCCESS)
94  DOCA_LOG_ERR("Failed to destroy vfs_dev: %s", doca_error_get_descr(res));
95  }
96 
97  if (state->rep != NULL) {
99  if (res != DOCA_SUCCESS)
100  DOCA_LOG_ERR("Failed to close DOCA Emulated Device representor: %s", doca_error_get_descr(res));
101 
102  state->rep = NULL;
103  }
104 
105  if (state->dev != NULL) {
106  res = doca_dev_close(state->dev);
107  if (res != DOCA_SUCCESS)
108  DOCA_LOG_ERR("Failed to close DOCA device: %s", doca_error_get_descr(res));
109 
110  state->dev = NULL;
111  }
112 
113  if (state->pe != NULL) {
114  res = doca_pe_destroy(state->pe);
115  if (res != DOCA_SUCCESS)
116  DOCA_LOG_ERR("Failed to destroy DOCA progress engine: %s", doca_error_get_descr(res));
117 
118  state->pe = NULL;
119  }
120 }
121 
122 /*
123  * Check if the device's PCI address matches the provided PCI address
124  * Supports both formats DOMAIN:BUS:DEVICE.FUNCTION or BUS:DEVICE.FUNCTION
125  *
126  * @devinfo [in]: The DOCA device information
127  * @pci_address [in]: The PCI address to compare against
128  * @return: true in case matches, false otherwise
129  */
130 static bool device_has_pci_addr(const struct doca_devinfo *devinfo, const char *pci_address)
131 {
132  uint8_t is_equal = 0;
133 
134  (void)doca_devinfo_is_equal_pci_addr(devinfo, pci_address, &is_equal);
135 
136  return is_equal == 1;
137 }
138 
139 /*
140  * Open a DOCA device according to a given PCI address
141  * Picks device that has given PCI address and supports hotplug of the PCI type
142  *
143  * @pci_address [in]: PCI address
144  * @dev [out]: pointer to doca_dev struct, NULL if not found
145  * @return: DOCA_SUCCESS on success and DOCA_ERROR otherwise
146  */
147 static doca_error_t find_doca_device(const char *pci_address, struct doca_dev **dev)
148 {
149  struct doca_devinfo **dev_list;
150  uint32_t nb_devs;
151  doca_error_t res;
152  size_t i;
153 
154  /* Set default return value */
155  *dev = NULL;
156 
157  res = doca_devinfo_create_list(&dev_list, &nb_devs);
158  if (res != DOCA_SUCCESS) {
159  DOCA_LOG_ERR("Failed to load doca devices list: %s", doca_error_get_descr(res));
160  return res;
161  }
162 
163  /* Search */
164  for (i = 0; i < nb_devs; i++) {
165  if (!device_has_pci_addr(dev_list[i], pci_address))
166  continue;
167 
168  res = doca_dev_open(dev_list[i], dev);
169  if (res == DOCA_SUCCESS) {
170  doca_devinfo_destroy_list(dev_list);
171  return res;
172  }
173  }
174 
175  DOCA_LOG_WARN("Matching device not found");
176 
177  doca_devinfo_destroy_list(dev_list);
178  return DOCA_ERROR_NOT_FOUND;
179 }
180 
181 /*
182  * Callback that is triggered everytime the hotplug state is changed for the associated emulated PCI device
183  *
184  * @pci_dev [in]: The associated emulated PCI device
185  * @user_data [in]: The user data that was previously provided along with callback
186  */
187 static void hotplug_event_handler_cb(struct doca_devemu_pci_dev *pci_dev, union doca_data user_data)
188 {
189  (void)user_data;
190 
191  enum doca_devemu_pci_hotplug_state hotplug_state;
192  doca_error_t res;
193 
194  DOCA_LOG_INFO("Emulated device's hotplug state has changed");
195 
196  res = doca_devemu_pci_dev_get_hotplug_state(pci_dev, &hotplug_state);
197  if (res != DOCA_SUCCESS) {
198  DOCA_LOG_ERR("Unable to get hotplug state: %s", doca_error_get_descr(res));
199  return;
200  }
201 
202  switch (hotplug_state) {
204  DOCA_LOG_INFO("Hotplug state changed to DOCA_DEVEMU_PCI_HP_STATE_POWER_OFF");
205  break;
207  DOCA_LOG_INFO("Hotplug state changed to DOCA_DEVEMU_PCI_HP_STATE_UNPLUG_IN_PROGRESS");
208  break;
210  DOCA_LOG_INFO("Hotplug state changed to DOCA_DEVEMU_PCI_HP_STATE_PLUG_IN_PROGRESS");
211  break;
213  DOCA_LOG_INFO("Hotplug state changed to DOCA_DEVEMU_PCI_HP_STATE_POWER_ON");
214  break;
215  default:
216  break;
217  }
218 }
219 
220 /*
221  * Register to the hotplug state change event of the emulated device
222  * After this the sample will be able to receive notification once the hotplug state of the emulated device
223  * has been changed. For possible states check out enum doca_devemu_pci_hotplug_state
224  *
225  * @pci_dev [in]: The emulated device context
226  * @cookie [in]: User data that is associated with the event, can be retrieved from event once callback is triggered
227  * @event [out]: The newly registered event object
228  * @return: DOCA_SUCCESS on success and DOCA_ERROR otherwise
229  */
230 static doca_error_t register_to_hotplug_state_change_events(struct doca_devemu_pci_dev *pci_dev, void *cookie)
231 {
232  union doca_data user_data;
233  doca_error_t res;
234 
235  user_data.ptr = cookie;
237  if (res != DOCA_SUCCESS) {
238  DOCA_LOG_ERR("Unable to allocate emulated PCI device hotplug state change event: %s",
239  doca_error_get_descr(res));
240  return res;
241  }
242 
243  return DOCA_SUCCESS;
244 }
245 
246 /*
247  * Run DOCA Device Emulation Create sample
248  *
249  * @pci_address [in]: Device PCI address
250  * @return: DOCA_SUCCESS on success and DOCA_ERROR otherwise
251  */
252 doca_error_t devemu_vfs_device_create(const char *pci_address)
253 {
255  uint8_t is_supported = 0;
256  size_t elapsed_time_in_micros;
257  struct timespec ts = {
258  .tv_sec = 0,
259  .tv_nsec = SLEEP_IN_NANOS,
260  };
261  struct program_state state = {0};
262 
263  /* Signal the while loop to stop and unplug the emulated device */
264  unplug_requested = false;
265  signal(SIGINT, signal_handler);
266  signal(SIGTERM, signal_handler);
267 
268  result = doca_pe_create(&state.pe);
269  if (result != DOCA_SUCCESS) {
270  DOCA_LOG_ERR("Unable to create progress engine: %s", doca_error_get_descr(result));
271  return result;
272  }
273 
274  result = find_doca_device(pci_address, &state.dev);
275  if (result != DOCA_SUCCESS) {
276  state_cleanup(&state);
277  return result;
278  }
279 
281  if (result != DOCA_SUCCESS) {
282  state_cleanup(&state);
283  return result;
284  }
285 
286  /* Check if hotplug supported */
289  &is_supported);
290  if (result != DOCA_SUCCESS) {
291  DOCA_LOG_ERR("Failed to check hotplug support: %s", doca_error_get_descr(result));
292  state_cleanup(&state);
293  return result;
294  }
295 
296  if (is_supported == 0) {
298  "Found device with matching address, but does not have hotplug support. Make sure a physical function was provided, and running with root permission");
299  state_cleanup(&state);
300  return result;
301  }
302 
303  /* Prepare emulated device before plugging it towards the host */
305  if (result != DOCA_SUCCESS) {
306  DOCA_LOG_ERR("Unable to create PCI emulated device representor: %s", doca_error_get_descr(result));
307  state_cleanup(&state);
308  return result;
309  }
310 
311  result = doca_devemu_vfs_dev_create(state.vfs_type, state.rep, state.pe, &state.vfs_dev);
312  if (result != DOCA_SUCCESS) {
313  DOCA_LOG_ERR("Unable to create VFS emulated device context: %s", doca_error_get_descr(result));
314  state_cleanup(&state);
315  return result;
316  }
317 
320 
323 
325  if (result != DOCA_SUCCESS) {
326  DOCA_LOG_ERR("Unable to start PCI emulated device context: %s", doca_error_get_descr(result));
327  state_cleanup(&state);
328  return result;
329  }
330 
331  /* Defer assignment so that cleanup does not stop the context in case it was not started */
332  state.ctx = doca_devemu_vfs_dev_as_ctx(state.vfs_dev);
333 
334  /* Register callback to be triggered once hotplug state changes */
336  if (result != DOCA_SUCCESS) {
337  state_cleanup(&state);
338  return result;
339  }
340 
342  if (result != DOCA_SUCCESS) {
343  state_cleanup(&state);
344  return result;
345  }
346 
347  DOCA_LOG_INFO("Hotplug initiated waiting for host to notice new device");
348  DOCA_LOG_INFO("Press ([ctrl] + c) to unplug device");
349 
350  /* Wait for emulated device to be hotplugged */
351  while (doca_pe_progress(state.pe) == 0) {
352  nanosleep(&ts, &ts);
353  if (unplug_requested)
354  break;
355  }
356 
357  while (!unplug_requested)
358  nanosleep(&ts, &ts);
359 
361  if (result != DOCA_SUCCESS) {
362  state_cleanup(&state);
363  return result;
364  }
365 
366  DOCA_LOG_INFO("Hotunplug initiated waiting for host to release device");
367 
368  /* Wait for emulated device to be unplugged */
369  elapsed_time_in_micros = 0;
370  while (doca_pe_progress(state.pe) == 0 && elapsed_time_in_micros < TIMEOUT_IN_MICROS) {
371  nanosleep(&ts, &ts);
372  elapsed_time_in_micros += SLEEP_IN_MICROS;
373  }
374 
375  /* Clean and destroy all relevant objects */
376  state_cleanup(&state);
377 
378  return result;
379 }
#define NULL
Definition: __stddef_null.h:26
int32_t result
static const char tag[DOCA_VFS_TAG_SIZE+1]
static void state_cleanup(struct program_state *state)
static doca_error_t register_to_hotplug_state_change_events(struct doca_devemu_pci_dev *pci_dev, void *cookie)
static bool device_has_pci_addr(const struct doca_devinfo *devinfo, const char *pci_address)
static void hotplug_event_handler_cb(struct doca_devemu_pci_dev *pci_dev, union doca_data user_data)
static bool unplug_requested
doca_error_t devemu_vfs_device_create(const char *pci_address)
static doca_error_t find_doca_device(const char *pci_address, struct doca_dev **dev)
DOCA_LOG_REGISTER(DPU_DEVEMU_VFS_DEVICE_CREATE)
static void signal_handler(int signum)
uint64_t cookie
DOCA_STABLE doca_error_t doca_ctx_start(struct doca_ctx *ctx)
Finalizes all configurations, and starts the DOCA CTX.
DOCA_STABLE doca_error_t doca_ctx_stop(struct doca_ctx *ctx)
Stops the context allowing reconfiguration.
DOCA_EXPERIMENTAL doca_error_t doca_devemu_pci_dev_create_rep(const struct doca_devemu_pci_type *pci_type, struct doca_dev_rep **dev_rep)
Create a new representor device for a given DOCA devemu PCI type.
DOCA_EXPERIMENTAL doca_error_t doca_devemu_pci_dev_destroy_rep(struct doca_dev_rep *rep_dev)
Destroy a representor device created by doca_devemu_pci_dev_create_rep().
DOCA_EXPERIMENTAL doca_error_t doca_devemu_pci_cap_type_is_hotplug_supported(const struct doca_devinfo *devinfo, const struct doca_devemu_pci_type *pci_type, uint8_t *supported)
Get the hotplug capability of the device for a given DOCA devemu PCI type.
DOCA_EXPERIMENTAL doca_error_t doca_devemu_pci_dev_hotplug(struct doca_devemu_pci_dev *pci_dev)
Issue hotplug procedure of the DOCA devemu PCI device.
DOCA_EXPERIMENTAL doca_error_t doca_devemu_pci_dev_get_hotplug_state(struct doca_devemu_pci_dev *pci_dev, enum doca_devemu_pci_hotplug_state *state)
Get the hotplug state of the DOCA devemu PCI device.
doca_devemu_pci_hotplug_state
DOCA devemu pci hotplug state.
DOCA_EXPERIMENTAL doca_error_t doca_devemu_pci_dev_event_hotplug_state_change_register(struct doca_devemu_pci_dev *pci_dev, doca_devemu_pci_dev_event_hotplug_state_change_handler_cb_t handler, union doca_data user_data)
Register to hotplug state changes.
DOCA_EXPERIMENTAL doca_error_t doca_devemu_pci_dev_hotunplug(struct doca_devemu_pci_dev *pci_dev)
Issue hot unplug procedure of the DOCA devemu PCI device.
@ DOCA_DEVEMU_PCI_HP_STATE_POWER_OFF
@ DOCA_DEVEMU_PCI_HP_STATE_UNPLUG_IN_PROGRESS
@ DOCA_DEVEMU_PCI_HP_STATE_PLUG_IN_PROGRESS
@ DOCA_DEVEMU_PCI_HP_STATE_POWER_ON
DOCA_EXPERIMENTAL struct doca_devemu_pci_type * doca_devemu_vfs_type_as_pci_type(struct doca_devemu_vfs_type *vfs_type)
Convert DOCA Virtio FS type instance into DOCA PCI type.
DOCA_EXPERIMENTAL doca_error_t doca_devemu_vfs_find_default_vfs_type_by_dev(struct doca_dev *dev, struct doca_devemu_vfs_type **vfs_type)
Find the default DOCA Virtio FS type associated with the device.
#define DOCA_VFS_TAG_SIZE
Size, in bytes, of the virtio FS tag in DOCA. According to the specification this is the name associa...
DOCA_EXPERIMENTAL struct doca_devemu_pci_dev * doca_devemu_vfs_dev_as_pci_dev(struct doca_devemu_vfs_dev *vfs_dev)
Convert DOCA Virtio FS device instance into DOCA devemu PCI device.
DOCA_EXPERIMENTAL doca_error_t doca_devemu_vfs_dev_set_num_request_queues(struct doca_devemu_vfs_dev *vfs_dev, uint32_t num_request_queues)
Set the value of the VIRTIO FS Device num_request_queues register.
DOCA_EXPERIMENTAL doca_error_t doca_devemu_vfs_dev_set_tag(struct doca_devemu_vfs_dev *vfs_dev, const char tag[DOCA_VFS_TAG_SIZE])
Set the value of the Virtio FS device tag. According to the specification the tag is encoded in UTF-8...
DOCA_EXPERIMENTAL doca_error_t doca_devemu_vfs_dev_destroy(struct doca_devemu_vfs_dev *vfs_dev)
Free a DOCA Virtio FS device object.
DOCA_EXPERIMENTAL doca_error_t doca_devemu_vfs_dev_create(struct doca_devemu_vfs_type *vfs_type, struct doca_dev_rep *dev_rep, struct doca_pe *progress_engine, struct doca_devemu_vfs_dev **vfs_dev)
Allocate DOCA Virtio FS device.
DOCA_EXPERIMENTAL struct doca_devemu_virtio_dev * doca_devemu_vfs_dev_as_virtio_dev(struct doca_devemu_vfs_dev *vfs_dev)
Convert DOCA Virtio FS device instance into DOCA Virtio device.
DOCA_EXPERIMENTAL struct doca_ctx * doca_devemu_vfs_dev_as_ctx(struct doca_devemu_vfs_dev *vfs_dev)
Convert DOCA Virtio FS device instance into DOCA context.
DOCA_EXPERIMENTAL doca_error_t doca_devemu_virtio_dev_set_queue_size(struct doca_devemu_virtio_dev *virtio_dev, uint16_t queue_size)
Set the Virtio max queue size for all Virtio queues.
DOCA_EXPERIMENTAL doca_error_t doca_devemu_virtio_dev_set_num_queues(struct doca_devemu_virtio_dev *virtio_dev, uint16_t num_queues)
Set the Virtio device num_queues register in common configuration structure according to Virtio speci...
DOCA_STABLE doca_error_t doca_devinfo_is_equal_pci_addr(const struct doca_devinfo *devinfo, const char *pci_addr_str, uint8_t *is_equal)
Check if a PCI address belongs to a DOCA devinfo.
DOCA_STABLE doca_error_t doca_devinfo_create_list(struct doca_devinfo ***dev_list, uint32_t *nb_devs)
Creates list of all available local devices.
DOCA_STABLE doca_error_t doca_devinfo_destroy_list(struct doca_devinfo **dev_list)
Destroy list of local device info structures.
DOCA_STABLE doca_error_t doca_dev_open(struct doca_devinfo *devinfo, struct doca_dev **dev)
Initialize local device for use.
DOCA_STABLE struct doca_devinfo * doca_dev_as_devinfo(const struct doca_dev *dev)
Get local device info from device. This should be useful when wanting to query information about devi...
DOCA_STABLE doca_error_t doca_dev_close(struct doca_dev *dev)
Destroy allocated local device instance.
enum doca_error doca_error_t
DOCA API return codes.
DOCA_STABLE const char * doca_error_get_descr(doca_error_t error)
Returns the description string of an error code.
@ DOCA_ERROR_NOT_FOUND
Definition: doca_error.h:54
@ DOCA_SUCCESS
Definition: doca_error.h:38
#define DOCA_LOG_ERR(format,...)
Generates an ERROR application log message.
Definition: doca_log.h:466
#define DOCA_LOG_WARN(format,...)
Generates a WARNING application log message.
Definition: doca_log.h:476
#define DOCA_LOG_INFO(format,...)
Generates an INFO application log message.
Definition: doca_log.h:486
DOCA_STABLE doca_error_t doca_pe_destroy(struct doca_pe *pe)
Destroy doca progress engine.
DOCA_STABLE uint8_t doca_pe_progress(struct doca_pe *pe)
Run the progress engine.
DOCA_STABLE doca_error_t doca_pe_create(struct doca_pe **pe)
Creates DOCA progress engine.
struct doca_devemu_vfs_dev * vfs_dev
struct doca_devemu_vfs_type * vfs_type
Convenience type for representing opaque data.
Definition: doca_types.h:56
void * ptr
Definition: doca_types.h:57