/* * Userland part of the hybrid TDMA/CSMA MAC processor which is responsible for sending the configuration at * the beginning of each time slot to the ATH9k WiFi driver using Netlink. * * build with: make all * * @authors S Zehl, A. Zubow */ #include #include #include #include #include #ifdef _EVENT_HAVE_SYS_TIME_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../backports-3.12.8-1/include/uapi/linux/nl80211.h" #include "iw.h" #include #include #include #include #include #include #include #include #include // struct to be passed via netlink to ath9k driver struct tid_sleep_tuple { char mac[6]; // destination MAC address char mask; // TID mask }; int mDebug = 0; int isRunning = 1; // Used by local controller for communication with mac processor int LOCAL_MAC_PROCESSOR_CTRL_PORT = 1217; // variables set at start-up long slotDuration = 10000; // mus int slotsPerFrame = 10; // e.g. 10 char * interface; // e.g. wifi0 char * configuration; // e.g. 1,mac_addr,tid_map;2,mac_addr,tid_map // updated at runtime std::string *schedule_per_slot = NULL; struct nl80211_state state; // internal state for keeping slotting time aligned long oldtime_l = 0; int event_is_persistent; const int estimatePrecisionEveryNslots = 1000; int times[estimatePrecisionEveryNslots]; int cnt = 0; long gl_clock = 0; // global clock w/ step size of slotDuration long slotCnt = 0; // total slot counter long frameCnt = 0; // total frame counter /////////////////////////////////////////////////////////////////////////////// /** helper functions for string manipulation */ std::vector &split(const std::string &s, char delim, std::vector &elems) { std::stringstream ss(s); std::string item; while (std::getline(ss, item, delim)) { elems.push_back(item); } return elems; } /** helper functions for string manipulation */ std::vector split(const std::string &s, char delim) { std::vector elems; split(s, delim, elems); return elems; } /** * Called at start-up or in case of reconfiguration. */ void updateSchedule(std::string &msg) { // init for (int i=0; i msg_parts = split(msg, '#'); for (int k=0; k entry = split(msg_parts[k], ','); int slot_id = atoi(entry[0].c_str()); const char * mac_addr = entry[1].c_str(); int tid_mask = atoi(entry[2].c_str()); std::stringstream ss; if (schedule_per_slot[slot_id].empty()) { ss << mac_addr << "," << entry[2].c_str(); } else { ss << schedule_per_slot[slot_id] << "#" << mac_addr << "," << entry[2].c_str(); } schedule_per_slot[slot_id] = ss.str(); } } /** Worker thread responsible for receiving new configuration updates */ void *worker_routine (void *arg) { std::cout << "Worker routine started ... ready to receive new configuration messages via ZMQ socket." << std::endl; zmq::context_t *context = (zmq::context_t *) arg; zmq::socket_t socket (*context, ZMQ_REP); std::stringstream ss; ss << "tcp://*:" << LOCAL_MAC_PROCESSOR_CTRL_PORT; socket.bind (ss.str().c_str()); //socket.bind ("ipc:///tmp/localmacprocessor"); while (true) { zmq::message_t request; // Wait for next request from client socket.recv (&request); std::string msg = std::string(static_cast(request.data()), request.size()); std::cout << "Received new configuration update: " << msg << std::endl; if (msg.find("TERMINATE") == 0) { // shutdown process isRunning = 0; } else { // update slot schedule updateSchedule(msg); } sleep (0.1); // Send ACK to client zmq::message_t reply(2); memcpy ((void *) reply.data(), "OK", 2); socket.send(reply); if(isRunning == 0) { socket.close(); break; } } return 0; } /** netlink error handling */ static int error_handler(struct sockaddr_nl *nla, struct nlmsgerr *err, void *arg) { int *ret = (int*) arg; *ret = err->error; printf("->CFG80211 returns: error: No:%d, %s\n",err->error, strerror((-1)*err->error)); return NL_STOP; } static int finish_handler(struct nl_msg *msg, void *arg) { int *ret = (int*) arg; *ret = 0; return NL_SKIP; } static int ack_handler(struct nl_msg *msg, void *arg) { int *ret = (int*) arg; *ret = 0; return NL_STOP; } static int send_nl_msg(std::string& schedule) { if (mDebug == 1) { std::cout << "Send schedule via netlink to ath9k driver: " << schedule << std::endl; } std::vector tuples = split(schedule, '#'); struct nl_cb *cb; struct nl_cb *s_cb; struct nl_msg *msg; signed long long devidx = 0; int err = 0; devidx = if_nametoindex(interface); msg = nlmsg_alloc(); if (!msg) { fprintf(stderr, "failed to allocate netlink message\n"); return 2; } cb = nl_cb_alloc(NL_CB_DEFAULT); s_cb = nl_cb_alloc(NL_CB_DEFAULT); if (!cb || !s_cb) { fprintf(stderr, "failed to allocate netlink callbacks\n"); err = 2; goto out_free_msg; } // create NetLink message genlmsg_put(msg, 0, 0, state.nl80211_id, 0, 0, NL80211_ATTR_TID_SLEEP_CTRL, 0); NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, devidx); // update tids_tuple with current slot schedule uint8_t mac_u8[6]; struct tid_sleep_tuple tids_tuple[tuples.size()]; for (int k=0; k tuple = split(tuples[k], ','); sscanf(tuple[0].c_str(), "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &mac_u8[0], &mac_u8[1], &mac_u8[2], &mac_u8[3], &mac_u8[4], &mac_u8[5]); int tid_mask = atoi(tuple[1].c_str()); for (int zz=0; zz<6; zz++) { tids_tuple[k].mac[zz] = mac_u8[zz]; } tids_tuple[k].mask = tid_mask; } NLA_PUT(msg, NL80211_ATTR_TID_SLEEP_CTRL_DATA, sizeof(struct tid_sleep_tuple)*tuples.size(), (const void *) &tids_tuple[0]); nl_socket_set_cb(state.nl_sock, s_cb); // send message err = nl_send_auto_complete(state.nl_sock, msg); if (err < 0) goto out; err = 1; nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &err); nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, finish_handler, &err); nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, &err); while (err > 0) { nl_recvmsgs(state.nl_sock, cb); } out: nl_cb_put(cb); out_free_msg: nlmsg_free(msg); return err; nla_put_failure: fprintf(stderr, "building message failed\n"); return 2; } /** initialize netlink */ static int nl80211_init(struct nl80211_state *state) { int err; printf("nl80211 init called v2\n"); state->nl_sock = nl_socket_alloc(); if (!state->nl_sock) { fprintf(stderr, "Failed to allocate netlink socket.\n"); return -ENOMEM; } nl_socket_set_buffer_size(state->nl_sock, 8192, 8192); if (genl_connect(state->nl_sock)) { fprintf(stderr, "Failed to connect to generic netlink.\n"); err = -ENOLINK; goto out_handle_destroy; } state->nl80211_id = genl_ctrl_resolve(state->nl_sock, "nl80211"); if (state->nl80211_id < 0) { fprintf(stderr, "nl80211 not found.\n"); err = -ENOENT; goto out_handle_destroy; } return 0; out_handle_destroy: nl_socket_free(state->nl_sock); return err; } /** timer callback called at the beginning of each new slot */ static void timeout_cb(evutil_socket_t fd, short event, void *arg) { struct timeval newtime, difference; struct event *timeout = (struct event *)arg; long error = 0; if (isRunning == 0) { printf("Terminating ...\n"); return; } evutil_gettimeofday(&newtime, NULL); long newtime_l = 1000000 * newtime.tv_sec + newtime.tv_usec; if (gl_clock == 0) { gl_clock = (newtime_l / slotDuration) * slotDuration; slotCnt = 0; frameCnt = 0; } else { gl_clock = gl_clock + (slotDuration % 1000000); slotCnt++; frameCnt = slotCnt / slotsPerFrame; } // current frame number long relFrameNum = slotCnt % slotsPerFrame; // estimate slot scheduling precision if(cnt < estimatePrecisionEveryNslots) { times[cnt] = newtime_l; cnt++; } else if(cnt == estimatePrecisionEveryNslots) { double sum = 0; for(int i = 1; i < estimatePrecisionEveryNslots; i++) { sum += times[i] - times[i-1]; } sum = sum / (estimatePrecisionEveryNslots-1); printf("Average slot duration: %.2f\n", sum); cnt = 0; } if (!event_is_persistent) { struct timeval tv; evutil_timerclear(&tv); if (oldtime_l != 0) { error = 2 * (newtime_l - gl_clock); } tv.tv_sec = slotDuration / 1000000; tv.tv_usec = (slotDuration % 1000000) - error; event_add(timeout, &tv); oldtime_l = newtime_l; send_nl_msg(schedule_per_slot[relFrameNum]); } } /** main entry point */ int main(int argc, char **argv) { struct event timeout; struct timeval tv; struct event_base *base; int flags; int err; char * inter_ptr; char * conf_ptr; int opt = 0; char *mac = NULL; char *guard_frames = NULL; char *best_effort = NULL; char *voice = NULL; char *pt; interface = '\0'; configuration = '\0'; while ((opt = getopt(argc, argv, "d:i:f:n:c:")) != -1) { switch(opt) { case 'd': mDebug = atoi(optarg); printf("\nDebug = %d", mDebug); break; case 'i': inter_ptr = optarg; interface = (char *) malloc(strlen(inter_ptr)); strcpy(interface, inter_ptr); printf("\nInterface = %s", interface); break; case 'f': slotDuration = atoi(optarg); printf("\nSlot Duration = %lu", slotDuration); break; case 'n': slotsPerFrame = atoi(optarg); printf("\nTotal number of slots in frame = %d", slotsPerFrame); break; case 'c': conf_ptr = optarg; configuration = (char *) malloc(strlen(conf_ptr)); strcpy(configuration, conf_ptr); printf("\nConfig = %s", configuration); break; case '?': printf("Usage: ./hybrid_tdma_csma_mac -i wifi0 -f 20000 -n 10\n"); break; } } // init schedule schedule_per_slot = new std::string[slotsPerFrame]; // update schedule std::string msg(configuration); updateSchedule(msg); if (interface == NULL) { printf("Error no interface supplied. Usage: ./hybrid_tdma_csma_mac -i wifi0 -f 20000 -n 10\n"); return -1; } printf("Using init schedule w/:\n"); for(int i=0; i<10; i++) { printf("#%d: %s, ", i, schedule_per_slot[i].c_str()); } // Prepare our context and sockets zmq::context_t context (1); try { pthread_t worker; pthread_create (&worker, NULL, worker_routine, (void *) &context); } catch (const std::exception& ex) { std::cerr << "Error setting up ZMQ: " << ex.what() << std::endl; return -1; } catch (...) { std::cerr << "Other strange error!!! " << std::endl; return -1; } err = nl80211_init(&state); event_is_persistent = 0; flags = 0; /* Initalize the event library */ base = event_base_new(); /* Initalize one event */ event_assign(&timeout, base, -1, flags, timeout_cb, (void*) &timeout); evutil_timerclear(&tv); /* align schedule to seconds */ struct timeval curTime; evutil_gettimeofday(&curTime, NULL); int mus = 1000000 - curTime.tv_usec; tv.tv_sec = 1; tv.tv_usec = mus; event_add(&timeout, &tv); event_base_dispatch(base); return (0); }