Current section
Files
Jump to
Current section
Files
c_src/excansock.c
#include <erl_nif.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <net/if.h>
#include <linux/can.h>
#include <linux/can/raw.h>
#include "excansock.h"
#define SET_NONBLOCKING(fd) fcntl((fd), F_SETFL, fcntl((fd), F_GETFL, 0) | O_NONBLOCK)
static ERL_NIF_TERM excansock_open_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
PrivData* priv = enif_priv_data(env);
ResourceData *resource_data;
ERL_NIF_TERM res;
ErlNifBinary dev_name;
int canfd;
if (argc != 2 ||
!enif_inspect_iolist_as_binary(env, argv[0], &dev_name) ||
!enif_get_int(env, argv[1], &canfd) ||
dev_name.size >= IF_NAMESIZE)
return enif_make_badarg(env);
int socket_fd = 0;
int dev_id = -1;
int r = 0;
if ((socket_fd = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0)
return enif_make_tuple2(env, priv->atom_error, socket_fd);
char dev_name_str[IF_NAMESIZE];
memcpy(dev_name_str, (const char *)dev_name.data, dev_name.size);
dev_name_str[dev_name.size] = '\0';
dev_id = if_nametoindex(dev_name_str);
if (!dev_id) {
printf("Device %s not found", dev_name_str);
close(socket_fd);
return enif_make_tuple2(env, priv->atom_error, enif_make_int(env, errno));
}
struct sockaddr_can addr;
addr.can_family = AF_CAN;
addr.can_ifindex = dev_id;
if ((r = bind(socket_fd, (struct sockaddr *)&addr, sizeof(addr))) < 0) {
close(socket_fd);
return enif_make_tuple2(env, priv->atom_error, enif_make_int(env, r));
}
if(canfd == 1 && setsockopt(socket_fd, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &canfd, sizeof(canfd)) < 0) {
close(socket_fd);
return enif_make_tuple2(env, priv->atom_error, priv->atom_enofd);
}
// Set minimum SO_SNDBUF, txqueuelen still needs to be set properly or
// write will return ENOBUFS instead EAGAIN when tx queue is full
int sndbuf = 0;
if ((r = setsockopt(socket_fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf))) < 0) {
close(socket_fd);
return enif_make_tuple2(env, priv->atom_error, r);
}
SET_NONBLOCKING(socket_fd);
resource_data = enif_alloc_resource(excansock_rt, sizeof(ResourceData));
resource_data->socket = socket_fd;
resource_data->canfd = canfd;
resource_data->write_mtx = enif_mutex_create("excansock.write");
resource_data->read_mtx = enif_mutex_create("excansock.read");
res = enif_make_resource(env, resource_data);
enif_release_resource(resource_data);
return enif_make_tuple2(env, priv->atom_ok, res);
}
static ERL_NIF_TERM excansock_close_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
PrivData* priv = enif_priv_data(env);
ResourceData *resource_data;
if (argc != 1 ||
!enif_get_resource(env, argv[0], excansock_rt, (void**)&resource_data) ||
resource_data->socket < 0)
return enif_make_badarg(env);
// if(close(resource_data->socket) < 0)
// return enif_make_tuple2(env, priv->atom_error, enif_make_int(env, errno));
// int r = enif_select(env, resource_data->socket, ERL_NIF_SELECT_STOP, resource_data, NULL, priv->atom_undefined);
// if(r < 0)
// return enif_make_tuple2(env, priv->atom_error, enif_make_int(env, r));
resource_data->socket = -1;
return priv->atom_ok;
}
static ERL_NIF_TERM excansock_recv_own_messages_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
PrivData* priv = enif_priv_data(env);
ResourceData *resource_data;
int val, r;
if (argc != 2 ||
!enif_get_resource(env, argv[0], excansock_rt, (void**)&resource_data) ||
!enif_get_int(env, argv[1], &val))
return enif_make_badarg(env);
r = setsockopt(resource_data->socket, SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS, &val, sizeof(val));
if(r < 0)
return enif_make_tuple2(env, priv->atom_error, enif_make_int(env, r));
return priv->atom_ok;
}
static ERL_NIF_TERM excansock_set_loopback_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
PrivData* priv = enif_priv_data(env);
ResourceData *resource_data;
int val, r;
if (argc != 2 ||
!enif_get_resource(env, argv[0], excansock_rt, (void**)&resource_data) ||
!enif_get_int(env, argv[1], &val))
return enif_make_badarg(env);
r = setsockopt(resource_data->socket, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &val, sizeof(val));
if(r < 0)
return enif_make_tuple2(env, priv->atom_error, enif_make_int(env, r));
return priv->atom_ok;
}
static ERL_NIF_TERM excansock_set_filters_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
PrivData* priv = enif_priv_data(env);
ResourceData *resource_data;
int r, i;
struct can_filter* rfilter;
unsigned int numfilter;
ERL_NIF_TERM res;
if (argc != 2 ||
!enif_get_resource(env, argv[0], excansock_rt, (void**)&resource_data) ||
!enif_get_list_length(env, argv[1], &numfilter))
return enif_make_badarg(env);
rfilter = malloc(sizeof(struct can_filter) * numfilter);
ERL_NIF_TERM item, items;
items = argv[1];
i = 0;
while(enif_get_list_cell(env, items, &item, &items)) {
const ERL_NIF_TERM *filter;
int arity;
if (!enif_get_tuple(env, item, &arity, &filter) ||
arity != 2 ||
!enif_get_uint(env, filter[0], &(rfilter[i].can_id)) ||
!enif_get_uint(env, filter[1], &(rfilter[i].can_mask))) {
res = enif_make_badarg(env);
goto done;
}
i++;
}
r = setsockopt(resource_data->socket, SOL_CAN_RAW, CAN_RAW_FILTER, rfilter, numfilter * sizeof(struct can_filter));
if(r < 0) {
res = enif_make_tuple2(env, priv->atom_error, enif_make_int(env, r));
goto done;
}
res = priv->atom_ok;
done:
free(rfilter);
return res;
}
static ERL_NIF_TERM excansock_set_error_filter_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
PrivData* priv = enif_priv_data(env);
ResourceData *resource_data;
int r;
can_err_mask_t val;
if (argc != 2 ||
!enif_get_resource(env, argv[0], excansock_rt, (void**)&resource_data) ||
!enif_get_uint(env, argv[1], &val))
return enif_make_badarg(env);
r = setsockopt(resource_data->socket, SOL_CAN_RAW, CAN_RAW_ERR_FILTER, &val, sizeof(val));
if(r < 0)
return enif_make_tuple2(env, priv->atom_error, enif_make_int(env, r));
return priv->atom_ok;
}
static ERL_NIF_TERM send_try_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
PrivData* priv = enif_priv_data(env);
ErlNifBinary data;
ERL_NIF_TERM res;
ResourceData *resource_data;
int r;
unsigned int can_id;
if (argc != 3 ||
!enif_get_resource(env, argv[0], excansock_rt, (void**)&resource_data) ||
!enif_get_uint(env,argv[1],&can_id) ||
!enif_inspect_iolist_as_binary(env,argv[2],&data) ||
(resource_data->canfd == 0 && data.size > CAN_MAX_DLEN) ||
(resource_data->canfd == 1 && data.size > CANFD_MAX_DLEN))
return enif_make_badarg(env);
enif_mutex_lock(resource_data->write_mtx);
if(data.size > CAN_MAX_DLEN) {
struct canfd_frame frame;
frame.can_id = can_id;
frame.len = data.size;
memcpy(frame.data, data.data, data.size);
r = write(resource_data->socket, &frame, sizeof(frame));
} else {
struct can_frame frame;
frame.can_id = can_id;
frame.can_dlc = data.size;
memcpy(frame.data, data.data, data.size);
r = write(resource_data->socket, &frame, sizeof(frame));
}
if (r < 0) {
if (errno != EAGAIN && errno != EINTR) {
res = enif_make_tuple2(env, priv->atom_error, enif_make_int(env,errno));
goto done;
}
} else {
res = priv->atom_ok;
goto done;
}
r = enif_select(env, resource_data->socket, ERL_NIF_SELECT_WRITE, resource_data, NULL, priv->atom_undefined);
if(r < 0) {
res = enif_make_tuple2(env, priv->atom_error, enif_make_int(env, r));
goto done;
}
res = priv->atom_eagain;
done:
enif_mutex_unlock(resource_data->write_mtx);
return res;
}
static ERL_NIF_TERM recv_try_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
PrivData* priv = enif_priv_data(env);
ERL_NIF_TERM res;
ResourceData *resource_data;
ErlNifBinary read_bin;
struct canfd_frame frame;
int r;
if (argc != 1 ||
!enif_get_resource(env, argv[0], excansock_rt, (void**)&resource_data))
return enif_make_badarg(env);
enif_mutex_lock(resource_data->read_mtx);
r = read(resource_data->socket, &frame, resource_data->canfd == 1 ? CANFD_MTU : CAN_MTU);
if (r == CAN_MTU || r == CANFD_MTU) {
enif_alloc_binary(frame.len, &read_bin);
memcpy(read_bin.data, frame.data, frame.len);
res = enif_make_tuple3(env,
priv->atom_can_frame,
enif_make_uint(env,frame.can_id),
enif_make_binary(env,&read_bin)
);
goto done;
} else if (errno != EAGAIN && errno != EWOULDBLOCK) {
res = enif_make_tuple2(env, priv->atom_error, enif_make_int(env,errno));
goto done;
}
r = enif_select(env, resource_data->socket, ERL_NIF_SELECT_READ, resource_data, NULL, priv->atom_undefined);
if(r < 0) {
res = enif_make_tuple2(env, priv->atom_error, enif_make_int(env, r));
goto done;
}
res = priv->atom_eagain;
done:
enif_mutex_unlock(resource_data->read_mtx);
return res;
}
/*
* NIF Callbacks
*/
static void excansock_rt_dtor(ErlNifEnv *env, ResourceData *obj) {
ResourceData* resource_data = (ResourceData*) obj;
enif_mutex_destroy(resource_data->read_mtx);
enif_mutex_destroy(resource_data->write_mtx);
if(resource_data->socket > -1) {
close(resource_data->socket);
}
}
static int load(ErlNifEnv* env, void** priv, ERL_NIF_TERM info) {
PrivData* data = enif_alloc(sizeof(PrivData));
if (data == NULL) {
return 1;
}
data->atom_ok = enif_make_atom(env, "ok");
data->atom_undefined = enif_make_atom(env, "undefined");
data->atom_error = enif_make_atom(env, "error");
data->atom_eagain = enif_make_atom(env, "eagain");
data->atom_can_frame = enif_make_atom(env, "can_frame");
data->atom_enofd = enif_make_atom(env, "enofd");
*priv = (void*) data;
excansock_rt = enif_open_resource_type(env, NULL, "excansock_resource", (ErlNifResourceDtor*)excansock_rt_dtor, ERL_NIF_RT_CREATE, NULL);
return !excansock_rt;
}
static int reload(ErlNifEnv* env, void** priv, ERL_NIF_TERM info) {
return load(env, priv, info);
}
static int upgrade(ErlNifEnv* env, void** priv, void** old_priv, ERL_NIF_TERM info) {
return load(env, priv, info);
}
static void unload(ErlNifEnv* env, void* priv) {
enif_free(priv);
}