| /* |
| * 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 |