635 lines
14 KiB
C++
635 lines
14 KiB
C++
/**
|
|
* \brief Linux emulation code
|
|
* \author Josef Soentgen
|
|
* \date 2014-08-04
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2014 Genode Labs GmbH
|
|
*
|
|
* This file is part of the Genode OS framework, which is distributed
|
|
* under the terms of the GNU General Public License version 2.
|
|
*/
|
|
|
|
/* Genode includes */
|
|
#include <base/printf.h>
|
|
|
|
/* local includes */
|
|
#include <lx.h>
|
|
|
|
#include <extern_c_begin.h>
|
|
# include <lx_emul.h>
|
|
# include <linux/socket.h>
|
|
# include <linux/net.h>
|
|
# include <net/sock.h>
|
|
#include <extern_c_end.h>
|
|
|
|
#include <wifi/socket_call.h>
|
|
|
|
|
|
static void run_socketcall(void *);
|
|
|
|
|
|
/* XXX move Wifi::Socket definition to better location */
|
|
struct Wifi::Socket
|
|
{
|
|
void *socket = nullptr;
|
|
bool non_block = false;
|
|
|
|
Socket() { }
|
|
|
|
explicit Socket(void *s) : socket(s) { }
|
|
};
|
|
|
|
|
|
struct Call
|
|
{
|
|
enum Opcode {
|
|
NONE, SOCKET, CLOSE,
|
|
BIND, GETSOCKNAME, RECVMSG, SENDMSG, SENDTO, SETSOCKOPT,
|
|
GET_MAC_ADDRESS, POLL_ALL, NON_BLOCK,
|
|
};
|
|
|
|
Opcode opcode = NONE;
|
|
Wifi::Socket *handle = nullptr;
|
|
|
|
union {
|
|
struct
|
|
{
|
|
int domain;
|
|
int type;
|
|
int protocol;
|
|
void *result;
|
|
} socket;
|
|
struct { /* no args */ } close;
|
|
struct
|
|
{
|
|
sockaddr const *addr;
|
|
int addrlen;
|
|
} bind;
|
|
struct
|
|
{
|
|
sockaddr *addr;
|
|
int *addrlen;
|
|
} getsockname;
|
|
struct
|
|
{
|
|
msghdr msg;
|
|
int flags;
|
|
iovec iov[Wifi::Msghdr::MAX_IOV_LEN];
|
|
} recvmsg;
|
|
struct
|
|
{
|
|
msghdr msg;
|
|
int flags;
|
|
iovec iov[Wifi::Msghdr::MAX_IOV_LEN];
|
|
} sendmsg;
|
|
struct {
|
|
int level;
|
|
int optname;
|
|
void const *optval;
|
|
uint32_t optlen;
|
|
} setsockopt;
|
|
struct
|
|
{
|
|
unsigned char *addr;
|
|
} get_mac_address;
|
|
struct
|
|
{
|
|
Wifi::Poll_socket_fd *sockets;
|
|
unsigned num;
|
|
int timeout;
|
|
} poll_all;
|
|
struct
|
|
{
|
|
bool value;
|
|
} non_block;
|
|
};
|
|
|
|
int err = 0;
|
|
};
|
|
|
|
static Call _call;
|
|
static Genode::Semaphore _block;
|
|
|
|
|
|
namespace Lx {
|
|
class Socket;
|
|
}
|
|
|
|
|
|
/**
|
|
* Context for socket calls
|
|
*/
|
|
class Lx::Socket
|
|
{
|
|
private:
|
|
|
|
|
|
Genode::Signal_transmitter _sender;
|
|
Genode::Signal_rpc_member<Lx::Socket> _dispatcher;
|
|
Lx::Task _task;
|
|
|
|
struct socket *_call_socket()
|
|
{
|
|
struct socket *sock = static_cast<struct socket*>(_call.handle->socket);
|
|
if (!sock)
|
|
PERR("BUG: sock is zero");
|
|
|
|
return sock;
|
|
}
|
|
|
|
void _do_socket()
|
|
{
|
|
struct socket *s;
|
|
int res = sock_create_kern(_call.socket.domain, _call.socket.type,
|
|
_call.socket.protocol, &s);
|
|
if (!res) {
|
|
_call.socket.result = s;
|
|
_call.err = 0;
|
|
return;
|
|
}
|
|
|
|
PERR("sock_create_kern failed, res: %d", res);
|
|
_call.socket.result = nullptr;
|
|
_call.err = res;
|
|
}
|
|
|
|
void _do_close()
|
|
{
|
|
struct socket *sock = _call_socket();
|
|
|
|
_call.err = sock->ops->release(sock);
|
|
}
|
|
|
|
void _do_bind()
|
|
{
|
|
struct socket *sock = _call_socket();
|
|
|
|
_call.err = sock->ops->bind(sock,
|
|
const_cast<struct sockaddr *>(_call.bind.addr),
|
|
_call.bind.addrlen);
|
|
}
|
|
|
|
void _do_getsockname()
|
|
{
|
|
struct socket *sock = _call_socket();
|
|
int addrlen = *_call.getsockname.addrlen;
|
|
|
|
_call.err = sock->ops->getname(sock, _call.getsockname.addr, &addrlen, 0);
|
|
|
|
*_call.getsockname.addrlen = addrlen;
|
|
}
|
|
|
|
void _do_recvmsg()
|
|
{
|
|
struct socket *sock = _call_socket();
|
|
struct msghdr *msg = &_call.recvmsg.msg;
|
|
|
|
/* needed by AF_NETLINK */
|
|
struct sock_iocb siocb;
|
|
Genode::memset(&siocb, 0, sizeof(struct sock_iocb));
|
|
struct kiocb kiocb;
|
|
Genode::memset(&kiocb, 0, sizeof(struct kiocb));
|
|
|
|
kiocb.private_ = &siocb;
|
|
|
|
if (_call.handle->non_block)
|
|
msg->msg_flags |= MSG_DONTWAIT;
|
|
|
|
size_t iovlen = 0;
|
|
for (size_t i = 0; i < msg->msg_iovlen; i++)
|
|
iovlen += msg->msg_iov[i].iov_len;
|
|
|
|
_call.err = sock->ops->recvmsg(&kiocb, sock, msg, iovlen, _call.recvmsg.flags);
|
|
}
|
|
|
|
void _do_sendmsg()
|
|
{
|
|
struct socket *sock = _call_socket();
|
|
struct msghdr *msg = const_cast<msghdr *>(&_call.sendmsg.msg);
|
|
|
|
/* needed by AF_NETLINK */
|
|
struct sock_iocb siocb;
|
|
Genode::memset(&siocb, 0, sizeof(struct sock_iocb));
|
|
struct kiocb kiocb;
|
|
Genode::memset(&kiocb, 0, sizeof(struct kiocb));
|
|
|
|
kiocb.private_ = &siocb;
|
|
|
|
if (_call.handle->non_block)
|
|
msg->msg_flags |= MSG_DONTWAIT;
|
|
|
|
size_t iovlen = 0;
|
|
for (size_t i = 0; i < msg->msg_iovlen; i++)
|
|
iovlen += msg->msg_iov[i].iov_len;
|
|
|
|
_call.err = sock->ops->sendmsg(&kiocb, sock, msg, iovlen);
|
|
}
|
|
|
|
void _do_setsockopt()
|
|
{
|
|
struct socket *sock = _call_socket();
|
|
|
|
/* taken from kernel_setsockopt() in net/socket.c */
|
|
if (_call.setsockopt.level == SOL_SOCKET)
|
|
_call.err = sock_setsockopt(sock,
|
|
_call.setsockopt.level,
|
|
_call.setsockopt.optname,
|
|
(char *)_call.setsockopt.optval,
|
|
_call.setsockopt.optlen);
|
|
else
|
|
_call.err = sock->ops->setsockopt(sock,
|
|
_call.setsockopt.level,
|
|
_call.setsockopt.optname,
|
|
(char *)_call.setsockopt.optval,
|
|
_call.setsockopt.optlen);
|
|
}
|
|
|
|
void _do_get_mac_address()
|
|
{
|
|
Lx::get_mac_address(_call.get_mac_address.addr);
|
|
}
|
|
|
|
void _do_poll_all()
|
|
{
|
|
Wifi::Poll_socket_fd *sockets = _call.poll_all.sockets;
|
|
unsigned num = _call.poll_all.num;
|
|
int timeout = _call.poll_all.timeout;
|
|
|
|
enum {
|
|
POLLIN_SET = (POLLRDNORM | POLLRDBAND | POLLIN | POLLHUP | POLLERR),
|
|
POLLOUT_SET = (POLLWRBAND | POLLWRNORM | POLLOUT | POLLERR),
|
|
POLLEX_SET = (POLLPRI)
|
|
};
|
|
|
|
int nready = 0;
|
|
bool timeout_triggered = false;
|
|
bool woken_up = false;
|
|
do {
|
|
/**
|
|
* Timeout was triggered, exit early.
|
|
*/
|
|
if (timeout_triggered)
|
|
break;
|
|
|
|
/**
|
|
* Poll each socket and check if there is something of interest.
|
|
*/
|
|
for (unsigned i = 0; i < num; i++) {
|
|
struct socket *sock = static_cast<struct socket*>(sockets[i].s->socket);
|
|
|
|
int mask = sock->ops->poll(0, sock, 0);
|
|
|
|
sockets[i].revents = 0;
|
|
if (mask & POLLIN_SET)
|
|
sockets[i].revents |= sockets[i].events & Wifi::WIFI_POLLIN ? Wifi::WIFI_POLLIN : 0;
|
|
if (mask & POLLOUT_SET)
|
|
sockets[i].revents |= sockets[i].events & Wifi::WIFI_POLLOUT ? Wifi::WIFI_POLLOUT : 0;
|
|
if (mask & POLLEX_SET)
|
|
sockets[i].revents |= sockets[i].events & Wifi::WIFI_POLLEX ? Wifi::WIFI_POLLEX : 0;
|
|
|
|
if (sockets[i].revents)
|
|
nready++;
|
|
}
|
|
|
|
/**
|
|
* We were woken up but there is still nothing of interest.
|
|
*/
|
|
if (woken_up)
|
|
break;
|
|
|
|
/**
|
|
* Exit the loop if either a socket is ready or there is
|
|
* no timeout given.
|
|
*/
|
|
if (nready || !timeout)
|
|
break;
|
|
|
|
/**
|
|
* In case of a timeout add all sockets to an artificial wait list
|
|
* so at least one is woken up an sk_data_ready() call.
|
|
*/
|
|
Lx::Task *task = Lx::scheduler().current();
|
|
struct socket_wq wq[num];
|
|
Lx::Task::List wait_list;
|
|
|
|
task->wait_enqueue(&wait_list);
|
|
for (unsigned i = 0; i < num; i++) {
|
|
struct socket *sock = static_cast<struct socket*>(sockets[i].s->socket);
|
|
wq[i].wait.list = &wait_list;
|
|
sock->sk->sk_wq = &wq[i];
|
|
}
|
|
|
|
long t = jiffies + msecs_to_jiffies(timeout);
|
|
timeout_triggered = !schedule_timeout(t);
|
|
|
|
task->wait_dequeue(&wait_list);
|
|
for (unsigned i = 0; i < num; i++) {
|
|
struct socket *sock = static_cast<struct socket*>(sockets[i].s->socket);
|
|
sock->sk->sk_wq = 0;
|
|
}
|
|
|
|
woken_up = true;
|
|
} while (1);
|
|
|
|
_call.err = nready;
|
|
}
|
|
|
|
void _do_non_block()
|
|
{
|
|
_call.handle->non_block = _call.non_block.value;
|
|
}
|
|
|
|
void _handle(unsigned)
|
|
{
|
|
_task.unblock();
|
|
Lx::scheduler().schedule();
|
|
}
|
|
|
|
public:
|
|
|
|
Socket(Server::Entrypoint &ep)
|
|
:
|
|
_dispatcher(ep, *this, &Lx::Socket::_handle),
|
|
_task(run_socketcall, nullptr, "socketcall",
|
|
Lx::Task::PRIORITY_0, Lx::scheduler())
|
|
{
|
|
_sender.context(_dispatcher);
|
|
}
|
|
|
|
void exec_call()
|
|
{
|
|
switch (_call.opcode) {
|
|
case Call::BIND: _do_bind(); break;
|
|
case Call::CLOSE: _do_close(); break;
|
|
case Call::GETSOCKNAME: _do_getsockname(); break;
|
|
case Call::POLL_ALL: _do_poll_all(); break;
|
|
case Call::RECVMSG: _do_recvmsg(); break;
|
|
case Call::SENDMSG: _do_sendmsg(); break;
|
|
case Call::SETSOCKOPT: _do_setsockopt(); break;
|
|
case Call::SOCKET: _do_socket(); break;
|
|
case Call::GET_MAC_ADDRESS: _do_get_mac_address(); break;
|
|
case Call::NON_BLOCK: _do_non_block(); break;
|
|
|
|
default:
|
|
_call.err = -EINVAL;
|
|
PWRN("unknown opcode: %u", _call.opcode);
|
|
break;
|
|
}
|
|
|
|
_call.opcode = Call::NONE;
|
|
_block.up();
|
|
}
|
|
|
|
void submit_and_block()
|
|
{
|
|
_sender.submit();
|
|
_block.down();
|
|
}
|
|
};
|
|
|
|
|
|
static Lx::Socket *_socket;
|
|
|
|
|
|
void Lx::socket_init(Server::Entrypoint &ep)
|
|
{
|
|
static Lx::Socket socket_ctx(ep);
|
|
_socket = &socket_ctx;
|
|
}
|
|
|
|
|
|
static void run_socketcall(void *)
|
|
{
|
|
while (1) {
|
|
Lx::scheduler().current()->block_and_schedule();
|
|
|
|
_socket->exec_call();
|
|
}
|
|
}
|
|
|
|
|
|
/**************************
|
|
** Socket_call instance **
|
|
**************************/
|
|
|
|
Wifi::Socket_call socket_call;
|
|
|
|
|
|
/***************************
|
|
** Socket_call interface **
|
|
***************************/
|
|
|
|
using namespace Wifi;
|
|
|
|
|
|
Wifi::Socket *Socket_call::socket(int domain, int type, int protocol)
|
|
{
|
|
/* FIXME domain, type, protocol values */
|
|
_call.opcode = Call::SOCKET;
|
|
_call.socket.domain = domain;
|
|
_call.socket.type = type;
|
|
_call.socket.protocol = protocol;
|
|
|
|
_socket->submit_and_block();
|
|
|
|
if (_call.socket.result == 0)
|
|
return 0;
|
|
|
|
Wifi::Socket *s = new (Genode::env()->heap()) Wifi::Socket(_call.socket.result);
|
|
|
|
return s;
|
|
}
|
|
|
|
|
|
int Socket_call::close(Socket *s)
|
|
{
|
|
_call.opcode = Call::CLOSE;
|
|
_call.handle = s;
|
|
|
|
_socket->submit_and_block();
|
|
|
|
if (_call.err)
|
|
PWRN("error %d on close()", _call.err);
|
|
|
|
destroy(Genode::env()->heap(), s);
|
|
return 0;
|
|
}
|
|
|
|
|
|
int Socket_call::bind(Socket *s, Wifi::Sockaddr const *addr, unsigned addrlen)
|
|
{
|
|
/* FIXME convert to/from Sockaddr */
|
|
_call.opcode = Call::BIND;
|
|
_call.handle = s;
|
|
_call.bind.addr = (sockaddr const *)addr;
|
|
_call.bind.addrlen = addrlen;
|
|
|
|
_socket->submit_and_block();
|
|
|
|
return _call.err;
|
|
}
|
|
|
|
|
|
int Socket_call::getsockname(Socket *s, Wifi::Sockaddr *addr, unsigned *addrlen)
|
|
{
|
|
/* FIXME convert to/from Sockaddr */
|
|
/* FIXME unsigned * -> int * */
|
|
_call.opcode = Call::GETSOCKNAME;
|
|
_call.handle = s;
|
|
_call.getsockname.addr = (sockaddr *)addr;
|
|
_call.getsockname.addrlen = (int *)addrlen;
|
|
|
|
_socket->submit_and_block();
|
|
|
|
return _call.err;
|
|
}
|
|
|
|
|
|
int Socket_call::poll_all(Poll_socket_fd *s, unsigned num, int timeout)
|
|
{
|
|
_call.opcode = Call::POLL_ALL;
|
|
_call.handle = 0;
|
|
_call.poll_all.sockets = s;
|
|
_call.poll_all.num = num;
|
|
_call.poll_all.timeout = timeout;
|
|
|
|
_socket->submit_and_block();
|
|
|
|
return _call.err;
|
|
}
|
|
|
|
|
|
static int msg_flags(Wifi::Flags in)
|
|
{
|
|
int out = Wifi::WIFI_F_NONE;
|
|
if (in & Wifi::WIFI_F_MSG_ERRQUEUE)
|
|
out |= MSG_ERRQUEUE;
|
|
|
|
return out;
|
|
};
|
|
|
|
|
|
Wifi::ssize_t Socket_call::recvmsg(Socket *s, Wifi::Msghdr *msg, Wifi::Flags flags)
|
|
{
|
|
_call.opcode = Call::RECVMSG;
|
|
_call.handle = s;
|
|
_call.recvmsg.msg.msg_name = msg->msg_name;
|
|
_call.recvmsg.msg.msg_namelen = msg->msg_namelen;
|
|
_call.recvmsg.msg.msg_iov = _call.recvmsg.iov;
|
|
_call.recvmsg.msg.msg_iovlen = msg->msg_iovlen;
|
|
_call.recvmsg.msg.msg_control = msg->msg_control;
|
|
_call.recvmsg.msg.msg_controllen = msg->msg_controllen;
|
|
_call.recvmsg.flags = msg_flags(flags);
|
|
|
|
for (unsigned i = 0; i < msg->msg_iovlen; ++i) {
|
|
_call.recvmsg.iov[i].iov_base = msg->msg_iov[i].iov_base;
|
|
_call.recvmsg.iov[i].iov_len = msg->msg_iov[i].iov_len;
|
|
}
|
|
|
|
_socket->submit_and_block();
|
|
|
|
msg->msg_namelen = _call.recvmsg.msg.msg_namelen;
|
|
|
|
return _call.err;
|
|
}
|
|
|
|
|
|
Wifi::ssize_t Socket_call::sendmsg(Socket *s, Wifi::Msghdr const *msg, Wifi::Flags flags)
|
|
{
|
|
_call.opcode = Call::SENDMSG;
|
|
_call.handle = s;
|
|
_call.sendmsg.msg.msg_name = msg->msg_name;
|
|
_call.sendmsg.msg.msg_namelen = msg->msg_namelen;
|
|
_call.sendmsg.msg.msg_iov = _call.sendmsg.iov;
|
|
_call.sendmsg.msg.msg_iovlen = msg->msg_iovlen;
|
|
_call.sendmsg.msg.msg_control = 0;
|
|
_call.sendmsg.msg.msg_controllen = 0;
|
|
_call.sendmsg.flags = msg_flags(flags);
|
|
|
|
for (unsigned i = 0; i < msg->msg_iovlen; ++i) {
|
|
_call.sendmsg.iov[i].iov_base = msg->msg_iov[i].iov_base;
|
|
_call.sendmsg.iov[i].iov_len = msg->msg_iov[i].iov_len;
|
|
}
|
|
|
|
_socket->submit_and_block();
|
|
|
|
return _call.err;
|
|
}
|
|
|
|
|
|
static int sockopt_level(Sockopt_level const in)
|
|
{
|
|
switch (in) {
|
|
case Wifi::WIFI_SOL_SOCKET: return SOL_SOCKET;
|
|
case Wifi::WIFI_SOL_NETLINK: return SOL_NETLINK;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
static int sockopt_name(Sockopt_level const level, Sockopt_name const in)
|
|
{
|
|
switch (level) {
|
|
case Wifi::WIFI_SOL_SOCKET:
|
|
switch (in) {
|
|
case Wifi::WIFI_SO_SNDBUF: return SO_SNDBUF;
|
|
case Wifi::WIFI_SO_RCVBUF: return SO_RCVBUF;
|
|
case Wifi::WIFI_SO_PASSCRED: return SO_PASSCRED;
|
|
case Wifi::WIFI_SO_WIFI_STATUS: return SO_WIFI_STATUS;
|
|
|
|
default: return -1;
|
|
}
|
|
case Wifi::WIFI_SOL_NETLINK:
|
|
switch (in) {
|
|
case Wifi::WIFI_NETLINK_ADD_MEMBERSHIP: return NETLINK_ADD_MEMBERSHIP;
|
|
case Wifi::WIFI_NETLINK_DROP_MEMBERSHIP: return NETLINK_DROP_MEMBERSHIP;
|
|
case Wifi::WIFI_NETLINK_PKTINFO: return NETLINK_PKTINFO;
|
|
|
|
default: return -1;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
int Socket_call::setsockopt(Socket *s,
|
|
Wifi::Sockopt_level level, Wifi::Sockopt_name optname,
|
|
const void *optval, uint32_t optlen)
|
|
{
|
|
/* FIXME optval values */
|
|
_call.opcode = Call::SETSOCKOPT;
|
|
_call.handle = s;
|
|
_call.setsockopt.level = sockopt_level(level);
|
|
_call.setsockopt.optname = sockopt_name(level, optname);
|
|
_call.setsockopt.optval = optval;
|
|
_call.setsockopt.optlen = optlen;
|
|
|
|
_socket->submit_and_block();
|
|
|
|
return _call.err;
|
|
}
|
|
|
|
|
|
void Socket_call::non_block(Socket *s, bool value)
|
|
{
|
|
_call.opcode = Call::NON_BLOCK;
|
|
_call.handle = s;
|
|
_call.non_block.value = value;
|
|
|
|
_socket->submit_and_block();
|
|
}
|
|
|
|
|
|
void Socket_call::get_mac_address(unsigned char *addr)
|
|
{
|
|
_call.opcode = Call::GET_MAC_ADDRESS;
|
|
_call.handle = 0;
|
|
_call.get_mac_address.addr = addr;
|
|
|
|
_socket->submit_and_block();
|
|
}
|