39 #ifndef LELY_IO_VCAN_BITRATE
41 #define LELY_IO_VCAN_BITRATE 1000000
44 #ifndef LELY_IO_VCAN_RXLEN
49 #define LELY_IO_VCAN_RXLEN 1024
64 static int io_vcan_ctrl_get_bitrate(
66 static int io_vcan_ctrl_set_bitrate(
io_can_ctrl_t *ctrl,
int nominal,
int data);
72 &io_vcan_ctrl_stopped,
73 &io_vcan_ctrl_restart,
74 &io_vcan_ctrl_get_bitrate,
75 &io_vcan_ctrl_set_bitrate,
76 &io_vcan_ctrl_get_state
115 static inline struct io_vcan_ctrl *io_vcan_ctrl_from_ctrl(
121 static void io_vcan_ctrl_signal(
struct spscring *ring,
void *arg);
127 static void io_vcan_ctrl_do_stop(
struct io_vcan_ctrl *vcan_ctrl);
131 static size_t io_vcan_chan_dev_cancel(
io_dev_t *dev,
struct ev_task *task);
132 static size_t io_vcan_chan_dev_abort(
io_dev_t *dev,
struct ev_task *task);
135 static const struct io_dev_vtbl io_vcan_chan_dev_vtbl = {
136 &io_vcan_chan_dev_get_ctx,
137 &io_vcan_chan_dev_get_exec,
138 &io_vcan_chan_dev_cancel,
139 &io_vcan_chan_dev_abort
144 static int io_vcan_chan_get_flags(
const io_can_chan_t *chan);
146 struct can_err *err,
struct timespec *tp,
int timeout);
147 static void io_vcan_chan_submit_read(
149 static int io_vcan_chan_write(
151 static void io_vcan_chan_submit_write(
156 &io_vcan_chan_get_dev,
157 &io_vcan_chan_get_flags,
159 &io_vcan_chan_submit_read,
161 &io_vcan_chan_submit_write
165 static void io_vcan_chan_svc_shutdown(
struct io_svc *svc);
168 static const struct io_svc_vtbl io_vcan_chan_svc_vtbl = {
170 &io_vcan_chan_svc_shutdown
232 static void io_vcan_chan_read_task_func(
struct ev_task *task);
233 static void io_vcan_chan_write_task_func(
struct ev_task *task);
236 static inline struct io_vcan_chan *io_vcan_chan_from_chan(
238 static inline struct io_vcan_chan *io_vcan_chan_from_svc(
241 static void io_vcan_chan_signal(
struct spscring *ring,
void *arg);
243 static void io_vcan_chan_do_pop(
struct io_vcan_chan *vcan,
247 static size_t io_vcan_chan_do_abort_tasks(
struct io_vcan_chan *vcan);
250 io_vcan_ctrl_alloc(
void)
262 io_vcan_ctrl_free(
void *ptr)
265 free(io_vcan_ctrl_from_ctrl(ptr));
272 struct io_vcan_ctrl *vcan = io_vcan_ctrl_from_ctrl(ctrl);
298 goto error_init_cond;
328 struct io_vcan_ctrl *vcan = io_vcan_ctrl_from_ctrl(ctrl);
363 io_vcan_ctrl_free((
void *)ctrl);
373 io_vcan_ctrl_fini(ctrl);
374 io_vcan_ctrl_free((
void *)ctrl);
381 struct io_vcan_ctrl *vcan = io_vcan_ctrl_from_ctrl(ctrl);
390 io_vcan_ctrl_do_stop(vcan);
411 return io_vcan_ctrl_write(ctrl, NULL, msg, NULL, timeout);
418 return io_vcan_ctrl_write(ctrl, NULL, NULL, err, timeout);
422 io_vcan_chan_alloc(
void)
434 io_vcan_chan_free(
void *ptr)
437 free(io_vcan_chan_from_chan(ptr));
444 struct io_vcan_chan *vcan = io_vcan_chan_from_chan(chan);
453 vcan->
dev_vptr = &io_vcan_chan_dev_vtbl;
462 vcan->
exec, &io_vcan_chan_read_task_func);
464 vcan->
exec, &io_vcan_chan_write_task_func);
470 goto error_alloc_rxbuf;
481 goto error_init_cond;
516 struct io_vcan_chan *vcan = io_vcan_chan_from_chan(chan);
523 io_vcan_chan_svc_shutdown(&vcan->
svc);
535 if (io_vcan_chan_do_abort_tasks(vcan))
541 "io_vcan_chan_fini() invoked with pending operations");
576 io_vcan_chan_free((
void *)chan);
586 io_vcan_chan_fini(chan);
587 io_vcan_chan_free((
void *)chan);
594 const struct io_vcan_chan *vcan = io_vcan_chan_from_chan(chan);
612 io_vcan_ctrl_insert(
ctrl, chan);
626 io_vcan_ctrl_remove(
ctrl, chan);
632 struct io_vcan_ctrl *vcan = io_vcan_ctrl_from_ctrl(ctrl);
637 io_vcan_ctrl_do_stop(vcan);
648 const struct io_vcan_ctrl *vcan = io_vcan_ctrl_from_ctrl(ctrl);
664 struct io_vcan_ctrl *vcan_ctrl = io_vcan_ctrl_from_ctrl(ctrl);
703 io_vcan_ctrl_get_bitrate(
const io_can_ctrl_t *ctrl,
int *pnominal,
int *pdata)
705 const struct io_vcan_ctrl *vcan = io_vcan_ctrl_from_ctrl(ctrl);
724 struct io_vcan_ctrl *vcan = io_vcan_ctrl_from_ctrl(ctrl);
729 io_vcan_ctrl_do_stop(vcan);
747 const struct io_vcan_ctrl *vcan = io_vcan_ctrl_from_ctrl(ctrl);
771 struct io_vcan_ctrl *vcan_ctrl = io_vcan_ctrl_from_ctrl(ctrl);
772 struct io_vcan_chan *vcan_chan = io_vcan_chan_from_chan(chan);
794 struct io_vcan_ctrl *vcan_ctrl = io_vcan_ctrl_from_ctrl(ctrl);
795 struct io_vcan_chan *vcan_chan = io_vcan_chan_from_chan(chan);
797 struct sllist read_queue, write_queue;
810 vcan_chan->
ctrl = NULL;
828 io_vcan_ctrl_signal(
struct spscring *ring,
void *arg)
865 struct io_vcan_ctrl *vcan_ctrl = io_vcan_ctrl_from_ctrl(ctrl);
866 assert(!msg || !err);
871 struct timespec ts = { 0, 0 };
893 if ((flags & vcan_ctrl->
flags) != flags) {
926 if (chan && chan == &vcan_chan->
chan_vptr)
945 &io_vcan_ctrl_signal, vcan_ctrl))
964 &vcan_ctrl->
cond, &vcan_ctrl->
mtx, &ts);
995 if (chan && chan == &vcan_chan->
chan_vptr)
1005 .is_err = 0, .u.msg = *msg, .ts = ts
1009 .is_err = 1, .u.err = *err, .ts = ts
1014 #if !LELY_NO_THREADS
1029 struct sllist read_queue, write_queue;
1036 #if !LELY_NO_THREADS
1044 #if !LELY_NO_THREADS
1054 io_vcan_chan_dev_get_ctx(
const io_dev_t *dev)
1056 const struct io_vcan_chan *vcan = io_vcan_chan_from_dev(dev);
1062 io_vcan_chan_dev_get_exec(
const io_dev_t *dev)
1064 const struct io_vcan_chan *vcan = io_vcan_chan_from_dev(dev);
1072 struct io_vcan_chan *vcan = io_vcan_chan_from_dev(dev);
1076 struct sllist read_queue, write_queue;
1080 #if !LELY_NO_THREADS
1083 io_vcan_chan_do_pop(vcan, &read_queue, &write_queue, task);
1089 #if !LELY_NO_THREADS
1093 size_t nread = io_can_chan_read_queue_post(
1095 n = n < SIZE_MAX - nread ? n + nread : SIZE_MAX;
1096 size_t nwrite = io_can_chan_write_queue_post(
1098 n = n < SIZE_MAX - nwrite ? n + nwrite : SIZE_MAX;
1106 struct io_vcan_chan *vcan = io_vcan_chan_from_dev(dev);
1111 #if !LELY_NO_THREADS
1114 io_vcan_chan_do_pop(vcan, &queue, &queue, task);
1115 #if !LELY_NO_THREADS
1125 const struct io_vcan_chan *vcan = io_vcan_chan_from_chan(chan);
1133 const struct io_vcan_chan *vcan_chan = io_vcan_chan_from_chan(chan);
1135 io_vcan_ctrl_from_ctrl(vcan_chan->
ctrl);
1137 return vcan_ctrl->
flags;
1142 struct timespec *tp,
int timeout)
1144 struct io_vcan_chan *vcan = io_vcan_chan_from_chan(chan);
1146 #if !LELY_NO_THREADS
1148 struct timespec ts = { 0, 0 };
1161 #if !LELY_NO_THREADS
1174 #if !LELY_NO_THREADS
1183 #if !LELY_NO_THREADS
1194 &io_vcan_chan_signal, vcan))
1202 #if !LELY_NO_THREADS
1208 #if !LELY_NO_THREADS
1210 #if !LELY_NO_TIMEOUT
1218 #if !LELY_NO_TIMEOUT
1228 int is_err = frame->is_err;
1230 *msg = frame->u.msg;
1231 else if (is_err && err)
1232 *err = frame->u.err;
1236 #if !LELY_NO_THREADS
1246 struct io_vcan_chan *vcan = io_vcan_chan_from_chan(chan);
1254 #if !LELY_NO_THREADS
1258 #if !LELY_NO_THREADS
1262 }
else if (!vcan->
ctrl) {
1263 #if !LELY_NO_THREADS
1268 #if !LELY_NO_THREADS
1278 #if !LELY_NO_THREADS
1295 return io_vcan_ctrl_write(ctrl, chan, msg, NULL, timeout);
1301 struct io_vcan_chan *vcan = io_vcan_chan_from_chan(chan);
1309 #if !LELY_NO_THREADS
1313 #if !LELY_NO_THREADS
1317 }
else if (!vcan->
ctrl) {
1318 #if !LELY_NO_THREADS
1323 #if !LELY_NO_THREADS
1333 #if !LELY_NO_THREADS
1343 io_vcan_chan_svc_shutdown(
struct io_svc *svc)
1348 #if !LELY_NO_THREADS
1356 io_vcan_chan_do_abort_tasks(vcan);
1357 #if !LELY_NO_THREADS
1363 io_vcan_chan_dev_cancel(dev, NULL);
1367 io_vcan_chan_read_task_func(
struct ev_task *task)
1373 #if !LELY_NO_THREADS
1387 io_vcan_chan_signal, vcan))
1397 if (!frame->is_err && read->
msg)
1398 *read->
msg = frame->u.msg;
1399 if (frame->is_err && read->
err)
1400 *read->
err = frame->u.err;
1402 *read->
tp = frame->ts;
1403 io_can_chan_read_post(read, !frame->is_err, 0);
1407 #if !LELY_NO_THREADS
1413 io_vcan_chan_write_task_func(
struct ev_task *
task)
1424 #if !LELY_NO_THREADS
1432 #if !LELY_NO_THREADS
1436 int result = io_vcan_chan_write(
1443 io_can_chan_write_post(write,
errc);
1444 #if !LELY_NO_THREADS
1467 #if !LELY_NO_THREADS
1471 if (
task && wouldblock)
1484 io_vcan_chan_from_dev(
const io_dev_t *dev)
1500 io_vcan_chan_from_svc(
const struct io_svc *
svc)
1508 io_vcan_chan_signal(
struct spscring *ring,
void *arg)
1514 #if !LELY_NO_THREADS
1520 #if !LELY_NO_THREADS
@ CAN_STATE_STOPPED
The device is stopped.
@ CAN_STATE_ACTIVE
The error active state (TX/RX error count < 128).
@ CAN_FLAG_FDF
The FD Format (FDF) flag, formerly known as Extended Data Length (EDL).
@ CAN_FLAG_BRS
The Bit Rate Switch (BRS) flag (only available in CAN FD format frames).
int io_clock_gettime(const io_clock_t *clock, struct timespec *tp)
Obtains the current time value of the specified clock.
const struct io_clock_vtbl *const io_clock_t
An abstract clock.
This header file is part of the I/O library; it contains the I/O context and service declarations.
void io_ctx_insert(io_ctx_t *ctx, struct io_svc *svc)
Registers an I/O service with an I/O context.
#define IO_SVC_INIT(vptr)
The static initializer for io_svc.
void io_ctx_remove(io_ctx_t *ctx, struct io_svc *svc)
Unregisters an I/O service with an I/O context.
This header file is part of the utilities library; it contains the diagnostic declarations.
void diag(enum diag_severity severity, int errc, const char *format,...)
Emits a diagnostic message.
int errnum2c(errnum_t errnum)
Transforms a platform-independent error number to a native error code.
@ ERRNUM_WOULDBLOCK
Operation would block.
@ ERRNUM_NETDOWN
Network is down.
@ ERRNUM_BADF
Bad file descriptor.
@ ERRNUM_INVAL
Invalid argument.
@ ERRNUM_AGAIN
Resource unavailable, try again.
@ ERRNUM_CANCELED
Operation canceled.
int get_errc(void)
Returns the last (thread-specific) native error code set by a system call or library function.
void set_errc(int errc)
Sets the current (thread-specific) native error code to errc.
int errno2c(int errnum)
Transforms a standard C error number to a native error code.
void set_errnum(errnum_t errnum)
Sets the current (thread-specific) platform-independent error number to errnum.
size_t ev_exec_abort(ev_exec_t *exec, struct ev_task *task)
Aborts the specified task submitted to *exec, if it has not yet begun executing, or all pending tasks...
void ev_exec_post(ev_exec_t *exec, struct ev_task *task)
Submits *task to *exec for execution.
void ev_exec_on_task_init(ev_exec_t *exec)
Indicates to the specified executor that a task will be submitted for execution in the future.
const struct ev_exec_vtbl *const ev_exec_t
An abstract task executor.
struct io_can_chan_write * io_can_chan_write_from_task(struct ev_task *task)
Obtains a pointer to a CAN channel write operation from a pointer to its completion task.
const struct io_can_chan_vtbl *const io_can_chan_t
An abstract CAN channel.
struct io_can_chan_read * io_can_chan_read_from_task(struct ev_task *task)
Obtains a pointer to a CAN channel read operation from a pointer to its completion task.
const struct io_can_ctrl_vtbl *const io_can_ctrl_t
An abstract CAN controller.
@ IO_CAN_BUS_FLAG_BRS
Bit Rate Switch support is enabled.
@ IO_CAN_BUS_FLAG_FDF
FD Format (formerly Extended Data Length) support is enabled.
@ IO_CAN_BUS_FLAG_ERR
Reception of error frames is enabled.
This is the public header file of the utilities library.
#define structof(ptr, type, member)
Obtains the address of a structure from the address of one of its members.
const struct io_dev_vtbl *const io_dev_t
An abstract I/O device.
int timespec_get(struct timespec *ts, int base)
Sets the interval at ts to hold the current calendar time based on the specified time base.
#define TIME_UTC
An integer constant greater than 0 that designates the UTC time base.
void sllist_init(struct sllist *list)
Initializes a singly-linked list.
struct sllist * sllist_append(struct sllist *dst, struct sllist *src)
Appends the singly-linked list at src to the one at dst.
void sllist_push_front(struct sllist *list, struct slnode *node)
Pushes a node to the front of a singly-linked list.
struct slnode * sllist_remove(struct sllist *list, struct slnode *node)
Removes a node from a singly-linked list.
#define sllist_foreach(list, node)
Iterates in order over each node in a singly-linked list.
void sllist_push_back(struct sllist *list, struct slnode *node)
Pushes a node to the back of a singly-linked list.
void slnode_init(struct slnode *node)
Initializes a node in a singly-linked list.
int sllist_empty(const struct sllist *list)
Returns 1 if the singly-linked list is empty, and 0 if not.
struct slnode * sllist_pop_front(struct sllist *list)
Pops a node from the front of a singly-linked list.
struct slnode * sllist_first(const struct sllist *list)
Returns a pointer to the first node in a singly-linked list.
This header file is part of the utilities library; it contains the single-producer,...
int spscring_p_abort_wait(struct spscring *ring)
Aborts a wait operation previously registered with spscring_p_submit_wait().
size_t spscring_c_alloc(struct spscring *ring, size_t *psize)
Allocates a consecutive range of indices, including wrapping, in a single-producer,...
int spscring_c_submit_wait(struct spscring *ring, size_t size, void(*func)(struct spscring *ring, void *arg), void *arg)
Checks if the requested range of indices, including wrapping, in a single-producer,...
size_t spscring_p_commit(struct spscring *ring, size_t size)
Makes the specified number of indices available to a consumer and, if this satisfies a wait operation...
int spscring_p_submit_wait(struct spscring *ring, size_t size, void(*func)(struct spscring *ring, void *arg), void *arg)
Checks if the requested range of indices, including wrapping, in a single-producer,...
size_t spscring_c_commit(struct spscring *ring, size_t size)
Makes the specified number of indices available to a producer and, if this satisfies a wait operation...
int spscring_c_abort_wait(struct spscring *ring)
Aborts a wait operation previously registered with spscring_c_submit_wait().
void spscring_init(struct spscring *ring, size_t size)
Initializes a single-producer, single-consumer ring buffer with the specified size.
size_t spscring_p_alloc(struct spscring *ring, size_t *psize)
Allocates a consecutive range of indices, including wrapping, in a single-producer,...
This is the internal header file of the CAN bus operation queue functions.
This header file is part of the C11 and POSIX compatibility library; it includes <stdint....
This header file is part of the C11 and POSIX compatibility library; it includes <stdlib....
int state
The state of the CAN node (one of CAN_STATE_ACTIVE, CAN_STATE_PASSIVE or CAN_STATE_BUSOFF).
A CAN or CAN FD format frame.
uint_least8_t flags
The flags (any combination of CAN_FLAG_IDE, CAN_FLAG_RTR, CAN_FLAG_FDF, CAN_FLAG_BRS and CAN_FLAG_ESI...
ev_exec_t * exec
A pointer to the executor to which the task is (to be) submitted.
A CAN channel read operation.
struct ev_task task
The task (to be) submitted upon completion (or cancellation) of the read operation.
struct timespec * tp
The address at which to store the system time at which the CAN frame or CAN error frame was received.
struct can_msg * msg
The address at which to store the CAN frame.
struct can_err * err
The address at which to store the CAN error frame.
A CAN channel write operation.
const struct can_msg * msg
A pointer to the CAN frame to be written.
struct ev_task task
The task (to be) submitted upon completion (or cancellation) of the write operation.
int errc
The error number, obtained as if by get_errc(), if an error occurred or the operation was canceled.
The virtual table of an I/O service.
The implementation of a virtual CAN channel.
struct io_vcan_frame * rxbuf
The receive queue.
struct ev_task read_task
The task responsible for initiating read operations.
unsigned shutdown
A flag indicating whether the I/O service has been shut down.
cnd_t cond
The condition variable used to wake up blocked synchronous read operations.
struct sllist read_queue
The queue containing pending read operations.
struct sllist write_queue
The queue containing pending write operations.
unsigned read_posted
A flag indicating whether read_task has been posted to exec.
struct io_svc svc
The I/O service representing the channel.
struct slnode node
The node of the channel in the list of the virtual CAN controller.
io_ctx_t * ctx
A pointer to the I/O context with which the channel is registered.
io_can_ctrl_t * ctrl
A pointer to the virtual CAN controller with which this channel is registered.
mtx_t mtx
The mutex protecting the channel and the queues of pending operations.
unsigned stopped
A flag indicating the virtual CAN controller is stopped.
struct spscring rxring
The ring buffer used to control the receive queue.
const struct io_can_chan_vtbl * chan_vptr
A pointer to the virtual table for the CAN channel interface.
unsigned write_posted
A flag indicating whether write_task has been posted to exec.
const struct io_dev_vtbl * dev_vptr
A pointer to the virtual table for the I/O device interface.
struct ev_task * current_write
The write operation currently being executed.
ev_exec_t * exec
A pointer to the executor used to execute all I/O tasks.
struct ev_task write_task
The task responsible for initiating write operations.
The implementation of a virtual CAN controller.
int state
The state of the virtual CAN bus.
int flags
The flags specifying which CAN bus features are enabled.
mtx_t mtx
The mutex protecting the controller and the list of virtual CAN channels.
int nominal
The nominal bitrate.
int data
The data bitrate.
int stopped
A flag indicating whether the controller is stopped.
cnd_t cond
The condition variable used to wake up blocked synchronous write operations.
io_clock_t * clock
A pointer to the clock used to obtain the timestamp when sending CAN frames.
const struct io_can_ctrl_vtbl * ctrl_vptr
A pointer to the virtual table for the CAN controller interface.
struct sllist list
The list of registered virtual CAN channels.
A node in a singly-linked list.
A single-producer, single-consumer ring buffer.
size_t ev_task_queue_abort(struct sllist *queue)
Aborts the tasks in queue by invoking ev_exec_on_task_fini() for each of them.
struct ev_task * ev_task_from_node(struct slnode *node)
Converts a pointer to a node in a queue to the address of the task containing the node.
#define EV_TASK_INIT(exec, func)
The static initializer for ev_task.
This header file is part of the C11 and POSIX compatibility library; it includes <threads....
int cnd_init(cnd_t *cond)
Creates a condition variable.
int cnd_timedwait(cnd_t *cond, mtx_t *mtx, const struct timespec *ts)
Atomically unlocks the mutex at mtx and endeavors to block until the condition variable at cond is si...
int cnd_broadcast(cnd_t *cond)
Unblocks all of the threads that are blocked on the condition variable at cond at the time of the cal...
int mtx_init(mtx_t *mtx, int type)
Creates a mutex object with properties indicated by type, which must have one of the four values:
int mtx_lock(mtx_t *mtx)
Blocks until it locks the mutex at mtx.
pthread_cond_t cnd_t
A complete object type that holds an identifier for a condition variable.
void cnd_destroy(cnd_t *cond)
Releases all resources used by the condition variable at cond.
int cnd_wait(cnd_t *cond, mtx_t *mtx)
Atomically unlocks the mutex at mtx and endeavors to block until the condition variable at cond is si...
void thrd_yield(void)
Endeavors to permit other threads to run, even if the current thread would ordinarily continue to run...
@ thrd_timedout
Indicates that the time specified in the call was reached without acquiring the requested resource.
@ thrd_success
Indicates that the requested operation succeeded.
int mtx_unlock(mtx_t *mtx)
Unlocks the mutex at mtx.
pthread_mutex_t mtx_t
A complete object type that holds an identifier for a mutex.
void mtx_destroy(mtx_t *mtx)
Releases any resources used by the mutex at mtx.
@ mtx_plain
A mutex type that supports neither timeout nor test and return.
This header file is part of the utilities library; it contains the time function declarations.
void timespec_add_msec(struct timespec *tp, uint_least64_t msec)
Adds msec milliseconds to the time at tp.
void io_vcan_chan_destroy(io_can_chan_t *chan)
Destroys a virtual CAN channel.
void io_vcan_ctrl_set_state(io_can_ctrl_t *ctrl, int state)
Sets the state of a virtual CAN bus: one of CAN_STATE_ACTIVE, CAN_STATE_PASSIVE, CAN_STATE_BUSOFF,...
#define LELY_IO_VCAN_BITRATE
The default bitrate of a virtual CAN bus.
void io_vcan_chan_close(io_can_chan_t *chan)
Closes a virtual CAN channel.
int io_vcan_ctrl_write_err(io_can_ctrl_t *ctrl, const struct can_err *err, int timeout)
Writes a CAN error frame to a all virtual CAN channels registered with a virtual CAN controller.
void io_vcan_chan_open(io_can_chan_t *chan, io_can_ctrl_t *ctrl)
Opens a virtual CAN channel by registering it with the specified virtual CAN controller.
int io_vcan_chan_is_open(const io_can_chan_t *chan)
Returns 1 if the CAN channel is open and 0 if not.
#define LELY_IO_VCAN_RXLEN
The default receive queue length (in number of CAN frames) of a vritual CAN channel.
io_can_ctrl_t * io_vcan_chan_get_ctrl(const io_can_chan_t *chan)
Returns a pointer to the virtual CAN controller with which a virtual CAN channel is registered,...
int io_vcan_ctrl_write_msg(io_can_ctrl_t *ctrl, const struct can_msg *msg, int timeout)
Writes a CAN frame to a all virtual CAN channels registered with a virtual CAN controller.
io_can_chan_t * io_vcan_chan_create(io_ctx_t *ctx, ev_exec_t *exec, size_t rxlen)
Creates a new virtual CAN channel.
io_can_ctrl_t * io_vcan_ctrl_create(io_clock_t *clock, int flags, int nominal, int data, int state)
Creates a new virtual CAN controller.
void io_vcan_ctrl_destroy(io_can_ctrl_t *ctrl)
Destroys a virtual CAN controller.
This header file is part of the I/O library; it contains the virtual CAN bus declarations.