| /* | |
| * Copyright (C) 2011 The Android Open Source Project | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| #define LOG_TAG "common_time" | |
| #include <utils/Log.h> | |
| #include <fcntl.h> | |
| #include <linux/in.h> | |
| #include <linux/tcp.h> | |
| #include <poll.h> | |
| #include <sys/socket.h> | |
| #include <sys/types.h> | |
| #include <unistd.h> | |
| #include <utils/Errors.h> | |
| #include <utils/misc.h> | |
| #include <common_time/local_clock.h> | |
| #include "common_clock.h" | |
| #include "diag_thread.h" | |
| #define kMaxEvents 16 | |
| #define kListenPort 9876 | |
| static bool setNonblocking(int fd) { | |
| int flags = fcntl(fd, F_GETFL); | |
| if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) { | |
| ALOGE("Failed to set socket (%d) to non-blocking mode (errno %d)", | |
| fd, errno); | |
| return false; | |
| } | |
| return true; | |
| } | |
| static bool setNodelay(int fd) { | |
| int tmp = 1; | |
| if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &tmp, sizeof(tmp)) < 0) { | |
| ALOGE("Failed to set socket (%d) to no-delay mode (errno %d)", | |
| fd, errno); | |
| return false; | |
| } | |
| return true; | |
| } | |
| namespace android { | |
| DiagThread::DiagThread(CommonClock* common_clock, LocalClock* local_clock) { | |
| common_clock_ = common_clock; | |
| local_clock_ = local_clock; | |
| listen_fd_ = -1; | |
| data_fd_ = -1; | |
| kernel_logID_basis_known_ = false; | |
| discipline_log_ID_ = 0; | |
| } | |
| DiagThread::~DiagThread() { | |
| } | |
| status_t DiagThread::startWorkThread() { | |
| status_t res; | |
| stopWorkThread(); | |
| res = run("Diag"); | |
| if (res != OK) | |
| ALOGE("Failed to start work thread (res = %d)", res); | |
| return res; | |
| } | |
| void DiagThread::stopWorkThread() { | |
| status_t res; | |
| res = requestExitAndWait(); // block until thread exit. | |
| if (res != OK) | |
| ALOGE("Failed to stop work thread (res = %d)", res); | |
| } | |
| bool DiagThread::openListenSocket() { | |
| bool ret = false; | |
| int flags; | |
| cleanupListenSocket(); | |
| if ((listen_fd_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { | |
| ALOGE("Socket failed."); | |
| goto bailout; | |
| } | |
| // Set non-blocking operation | |
| if (!setNonblocking(listen_fd_)) | |
| goto bailout; | |
| struct sockaddr_in addr; | |
| memset(&addr, 0, sizeof(addr)); | |
| addr.sin_family = AF_INET; | |
| addr.sin_addr.s_addr = INADDR_ANY; | |
| addr.sin_port = htons(kListenPort); | |
| if (bind(listen_fd_, (struct sockaddr*)&addr, sizeof(addr)) < 0) { | |
| ALOGE("Bind failed."); | |
| goto bailout; | |
| } | |
| if (listen(listen_fd_, 1) < 0) { | |
| ALOGE("Listen failed."); | |
| goto bailout; | |
| } | |
| ret = true; | |
| bailout: | |
| if (!ret) | |
| cleanupListenSocket(); | |
| return ret; | |
| } | |
| void DiagThread::cleanupListenSocket() { | |
| if (listen_fd_ >= 0) { | |
| int res; | |
| struct linger l; | |
| l.l_onoff = 1; | |
| l.l_linger = 0; | |
| setsockopt(listen_fd_, SOL_SOCKET, SO_LINGER, &l, sizeof(l)); | |
| shutdown(listen_fd_, SHUT_RDWR); | |
| close(listen_fd_); | |
| listen_fd_ = -1; | |
| } | |
| } | |
| void DiagThread::cleanupDataSocket() { | |
| if (data_fd_ >= 0) { | |
| int res; | |
| struct linger l; | |
| l.l_onoff = 1; | |
| l.l_linger = 0; | |
| setsockopt(data_fd_, SOL_SOCKET, SO_LINGER, &l, sizeof(l)); | |
| shutdown(data_fd_, SHUT_RDWR); | |
| close(data_fd_); | |
| data_fd_ = -1; | |
| } | |
| } | |
| void DiagThread::resetLogIDs() { | |
| // Drain and discard all of the events from the kernel | |
| struct local_time_debug_event events[kMaxEvents]; | |
| while(local_clock_->getDebugLog(events, kMaxEvents) > 0) | |
| ; | |
| { | |
| Mutex::Autolock lock(&discipline_log_lock_); | |
| discipline_log_.clear(); | |
| discipline_log_ID_ = 0; | |
| } | |
| kernel_logID_basis_known_ = false; | |
| } | |
| void DiagThread::pushDisciplineEvent(int64_t observed_local_time, | |
| int64_t observed_common_time, | |
| int64_t nominal_common_time, | |
| int32_t total_correction, | |
| int32_t rtt) { | |
| Mutex::Autolock lock(&discipline_log_lock_); | |
| DisciplineEventRecord evt; | |
| evt.event_id = discipline_log_ID_++; | |
| evt.action_local_time = local_clock_->getLocalTime(); | |
| common_clock_->localToCommon(evt.action_local_time, | |
| &evt.action_common_time); | |
| evt.observed_local_time = observed_local_time; | |
| evt.observed_common_time = observed_common_time; | |
| evt.nominal_common_time = nominal_common_time; | |
| evt.total_correction = total_correction; | |
| evt.rtt = rtt; | |
| discipline_log_.push_back(evt); | |
| while (discipline_log_.size() > kMaxDisciplineLogSize) | |
| discipline_log_.erase(discipline_log_.begin()); | |
| } | |
| bool DiagThread::threadLoop() { | |
| struct pollfd poll_fds[1]; | |
| if (!openListenSocket()) { | |
| ALOGE("Failed to open listen socket"); | |
| goto bailout; | |
| } | |
| while (!exitPending()) { | |
| memset(&poll_fds, 0, sizeof(poll_fds)); | |
| if (data_fd_ < 0) { | |
| poll_fds[0].fd = listen_fd_; | |
| poll_fds[0].events = POLLIN; | |
| } else { | |
| poll_fds[0].fd = data_fd_; | |
| poll_fds[0].events = POLLRDHUP | POLLIN; | |
| } | |
| int poll_res = poll(poll_fds, NELEM(poll_fds), 50); | |
| if (poll_res < 0) { | |
| ALOGE("Fatal error (%d,%d) while waiting on events", | |
| poll_res, errno); | |
| goto bailout; | |
| } | |
| if (exitPending()) | |
| break; | |
| if (poll_fds[0].revents) { | |
| if (poll_fds[0].fd == listen_fd_) { | |
| data_fd_ = accept(listen_fd_, NULL, NULL); | |
| if (data_fd_ < 0) { | |
| ALOGW("Failed accept on socket %d with err %d", | |
| listen_fd_, errno); | |
| } else { | |
| if (!setNonblocking(data_fd_)) | |
| cleanupDataSocket(); | |
| if (!setNodelay(data_fd_)) | |
| cleanupDataSocket(); | |
| } | |
| } else | |
| if (poll_fds[0].fd == data_fd_) { | |
| if (poll_fds[0].revents & POLLRDHUP) { | |
| // Connection hung up; time to clean up. | |
| cleanupDataSocket(); | |
| } else | |
| if (poll_fds[0].revents & POLLIN) { | |
| uint8_t cmd; | |
| if (read(data_fd_, &cmd, sizeof(cmd)) > 0) { | |
| switch(cmd) { | |
| case 'r': | |
| case 'R': | |
| resetLogIDs(); | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| struct local_time_debug_event events[kMaxEvents]; | |
| int amt = local_clock_->getDebugLog(events, kMaxEvents); | |
| if (amt > 0) { | |
| for (int i = 0; i < amt; i++) { | |
| struct local_time_debug_event& e = events[i]; | |
| if (!kernel_logID_basis_known_) { | |
| kernel_logID_basis_ = e.local_timesync_event_id; | |
| kernel_logID_basis_known_ = true; | |
| } | |
| char buf[1024]; | |
| int64_t common_time; | |
| status_t res = common_clock_->localToCommon(e.local_time, | |
| &common_time); | |
| snprintf(buf, sizeof(buf), "E,%lld,%lld,%lld,%d\n", | |
| e.local_timesync_event_id - kernel_logID_basis_, | |
| e.local_time, | |
| common_time, | |
| (OK == res) ? 1 : 0); | |
| buf[sizeof(buf) - 1] = 0; | |
| if (data_fd_ >= 0) | |
| write(data_fd_, buf, strlen(buf)); | |
| } | |
| } | |
| { // scope for autolock pattern | |
| Mutex::Autolock lock(&discipline_log_lock_); | |
| while (discipline_log_.size() > 0) { | |
| char buf[1024]; | |
| DisciplineEventRecord& e = *discipline_log_.begin(); | |
| snprintf(buf, sizeof(buf), | |
| "D,%lld,%lld,%lld,%lld,%lld,%lld,%d,%d\n", | |
| e.event_id, | |
| e.action_local_time, | |
| e.action_common_time, | |
| e.observed_local_time, | |
| e.observed_common_time, | |
| e.nominal_common_time, | |
| e.total_correction, | |
| e.rtt); | |
| buf[sizeof(buf) - 1] = 0; | |
| if (data_fd_ >= 0) | |
| write(data_fd_, buf, strlen(buf)); | |
| discipline_log_.erase(discipline_log_.begin()); | |
| } | |
| } | |
| } | |
| bailout: | |
| cleanupDataSocket(); | |
| cleanupListenSocket(); | |
| return false; | |
| } | |
| } // namespace android |