diff --git a/.editorconfig b/.editorconfig index 730d51b8be..ab2a20ec8e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,6 +7,8 @@ end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true +draw_white_space = all +trim_trailing_white_space_on_save = true [Makefile] indent_style = tab diff --git a/.gitignore b/.gitignore index 1a9310d95c..301550c034 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ *.vmdk *.tmp *.d +*.pyc nbproject src/bootloader diff --git a/.gitmodules b/.gitmodules index 811cbbf272..73399617e9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,10 +1,10 @@ [submodule "mod/rapidjson"] path = mod/rapidjson - url = git@github.com:fwsGonzo/rapidjson.git + url = https://github.com/fwsGonzo/rapidjson.git [submodule "mod/GSL"] path = mod/GSL - url = git@github.com:Microsoft/GSL.git + url = https://github.com/Microsoft/GSL.git [submodule "test/lest"] path = test/lest - url = git@github.com:martinmoene/lest.git + url = https://github.com/martinmoene/lest.git diff --git a/CONTRIBUTE.md b/CONTRIBUTE.md index e0dc70cf62..2e8db55a28 100644 --- a/CONTRIBUTE.md +++ b/CONTRIBUTE.md @@ -1,14 +1,14 @@ # Contributing to IncludeOS -Feel free to [clone, edit and send pull-request](https://help.github.com/articles/using-pull-requests). +Feel free to [clone, edit and send pull-request](https://help.github.com/articles/using-pull-requests). -* Send any and all pull-requests to the dev-branch. It's ok if it comes from your master branch. -* Do exactly one thing pr. pull-request. This makes it possible to quickly see and understand what you've done. +* Send any and all pull-requests to the [dev-branch](https://github.com/hioa-cs/IncludeOS/tree/dev). It's ok if it comes from your master branch. +* Do "one thing" pr. pull-request. This makes it possible to quickly see and understand what you've done. * Please don't redo the folder-structure - if you have suggestions for this, just post an issue explaining the benefits of your suggested structure. -* Everything you commit will be under the same license as this repo (Undecided so far) and HiOA will retain the right to publish your commits under a different license. +* Everything you commit will be under the same license as this repo and the copyright holders will retain the right to publish your commits under a different license. ### Consider making a standalone module! -We're working on a github-based package manager, much like [npm](https://www.npmjs.com/). Most new funcitonality from us, such as HTTP and a REST-framework, will probably come out like separate packages - each with their own repository. This will help keep the IncludeOS core small, and easier to maintain. Clearly, we also want to gather everything in one place, and our upcoming package manager will be doing that. Meanwhile: If you do want make a module - just make it a separate github-repo, and let us know about it. We'll link to it from here, until the package manager is ready. +We're working on a github-based package manager, much like [npm](https://www.npmjs.com/). Most new functionality from us, such as HTTP and a REST-framework, will probably come out like separate packages - each with their own repository. This will help keep the IncludeOS core small, and easier to maintain. Clearly, we also want to gather everything in one place, and our upcoming package manager will be doing that. Meanwhile: If you do want make a module - just make it a separate github-repo, and let us know about it. We'll link to it from here, until the package manager is ready. ## Issue tracker Post any issues not already mentioned, in the [issue tracker](https://github.com/hioa-cs/IncludeOS/issues). You can also post questions, not answered in the [FAQ](https://github.com/hioa-cs/IncludeOS/wiki/FAQ). diff --git a/README.md b/README.md index c7c444d4f4..fe4f764c90 100644 --- a/README.md +++ b/README.md @@ -15,26 +15,31 @@ IncludeOS is free software, with "no warranties or restrictions of any kind". ## Build status -We're working towards automating everything with our Jenkins CI server. Unfortunately he was recently hacked and is being cloned from DNA recovered from a mosqito in resin plus pig stem cells as we speak. He'll be back on his feet and just like before (ish) pretty soon. +We're working towards automating everything with our Jenkins CI server. The tests performed to generate these badges are taken from the tests folder. More tests are added regularly, so to see which tests have been completed to generate the results click the corresponding badge. + +| | Build from bundle | Build from source | +|--------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------| +| Master | [![Build Status](https://jenkins.includeos.org/buildStatus/icon?job=shield_master_bundle_pipe)](https://jenkins.includeos.org/job/shield_master_bundle_pipe/) | Coming soon | +| Dev | [![Build Status](https://jenkins.includeos.org/buildStatus/icon?job=shield_dev_bundle_pipe)](https://jenkins.includeos.org/job/shield_dev_bundle_pipe/) | Coming soon | ### Key features * **Extreme memory footprint**: A minimal bootable image, including bootloader, operating system components and a complete C++ standard library is currently 693K when optimized for size. * **KVM and VirtualBox support** with full virtualization, using [x86 hardware virtualization](https://en.wikipedia.org/wiki/X86_virtualization) whenever available (it is on most modern x86 CPU's). In principle IncludeOS should run on any x86 hardware platform, even on a physical x86 computer, given appropriate drivers. Officially, we develop for- and test on [Linux KVM](http://www.linux-kvm.org/page/Main_Page), which power the [OpenStack IaaS cloud](https://www.openstack.org/), and [VirtualBox](https://www.virtualbox.org), which means that you can run your IncludeOS service on both Linux, Microsoft Windows and Apple OS X. * **C++11/14 support** - * Full C++11/14 language support with [clang](http://clang.llvm.org) v3.6 and later. - * Standard C++ library** (STL) [libc++](http://libcxx.llvm.org) from [LLVM](http://llvm.org/) + * Full C++11/14 language support with [clang](http://clang.llvm.org) v3.8 and later. + * Standard C++ library (STL) [libc++](http://libcxx.llvm.org) from [LLVM](http://llvm.org/) * Exceptions and stack unwinding (currently using [libgcc](https://gcc.gnu.org/onlinedocs/gccint/Libgcc.html)) * *Note:* Certain language features, such as threads and filestreams are currently missing backend support. * **Standard C library** using [newlib](https://sourceware.org/newlib/) from [Red Hat](http://www.redhat.com/) * **Virtio Network driver** with DMA. [Virtio](https://www.oasis-open.org/committees/tc_home.php?wg_abbrev=virtio) provides a highly efficient and widely supported I/O virtualization. Like most implementations IncludeOS currently uses "legacy mode", but we're working towards the new [Virtio 1.0 OASIS standard](http://docs.oasis-open.org/virtio/virtio/v1.0/virtio-v1.0.html) * **A highly modular TCP/IP-stack** written from scratch, still under heavy development. - * TCP: Just enough to serve HTTP - * UDP: Enough to support a high performance DNS service - * DHCP: Basic support, tested on VirtualBox and KVM - * ICMP: Enough to answer ping, no control messages yet + * TCP: Not all the RFC's are impelemented yet, but it's stable and usable, including congestion control and common options. + * UDP: More or less complete, enough to support a high performance DNS service + * DHCP: Basic support, tested on VirtualBox, KVM and OpenStack + * ICMP: Enough to answer ping, no integration with the rest of the stack yet. * ARP * Ethernet - * IPv6 support under active development + * IPv6 support under development A longer list of features and limitations is on the [wiki feature list](https://github.com/hioa-cs/IncludeOS/wiki/Features) @@ -65,14 +70,13 @@ You can now log in to the vagrant build environment and build and run a test ser $ ./test.sh ``` - ## Prerequisites for building IncludeOS VM's - * **Ubuntu 14.04 LTS x86_64**, Vanilla, either on a physical or virtual machine (A virtualbox VM works fine) + * **Ubuntu 16.04 or 14.04 LTS, x86_64**, either on a physical or virtual machine (A virtualbox VM works fine) * For the full source build, you'll need at least 1024 MB memory - * In order to support VGA graphics inside a VM, we recommend a lightweight GUI, such as [lubuntu](http://lubuntu.net/blog/lubuntu-1404-trusty-tahr-released) which runs great inside a virtual machine. + * In order to support VGA graphics inside a VM, we recommend a lightweight GUI, such as [lubuntu](https://help.ubuntu.com/community/Lubuntu/GetLubuntu) which runs great inside a virtual machine. * *NOTE:* Graphics is by no means necessary, as all IncludeOS output by default will be routed to the serial port, and in Qemu, * The install scripts may very well work on other flavours on Linux, but we haven't tried. Please let us know if you do. - * **Building on a Mac:** we have done a successful build from bundle, directly on a Mac. It's a work in progress, but see [./etc/install_osx.sh](./etc/install_osx.sh) for details. + * **Building on a Mac:** you can build IncludeOS (from bundle only) directly on a Mac by running [./etc/install_osx.sh](./etc/install_osx.sh). * You'll need `git` to clone from github. Once you have a system with the prereqs (virtual or not), you can choose a full build from source, or a fast build from binaries: @@ -84,7 +88,7 @@ Once you have a system with the prereqs (virtual or not), you can choose a full $ ./etc/install_from_bundle.sh **The script will:** -* Install the required dependencies: `curl make clang-3.6 nasm bridge-utils qemu` +* Install the required dependencies: `curl make clang-3.8 nasm bridge-utils qemu` * Download the latest binary release bundle from github, using the github API. * Unzip the bundle to `$INCLUDEOS_INSTALL_LOC` - which you can set in advance, or which defaults to `$HOME` * Create a network bridge called `include0`, for tap-networking @@ -102,7 +106,7 @@ About a minute or two (On a 4-core virtualbox Ubuntu VM, runing on a 2015 MacBoo **The script will:** * Install all the tools required for building IncludeOS, and all libraries it depends on: - * `build-essential make nasm texinfo clang-3.6 cmake ninja-build subversion zlib1g-dev libtinfo-dev` + * `build-essential make nasm texinfo clang-3.8 cmake ninja-build subversion zlib1g-dev libtinfo-dev` * Build a GCC cross compiler along the lines of the [osdev howto](http://wiki.osdev.org/GCC_Cross-Compiler) which we really only need to build `libgcc` and `newlib`. * Build [Redhat's newlib](https://sourceware.org/newlib/) using the cross compiler, and install it according to `./etc/build_newlib.sh`. The script will also install it to the mentioned location. * Build a 32-bit version of [LLVM's libc++](http://libcxx.llvm.org/) tailored for IncludeOS. diff --git a/api/async b/api/async new file mode 100644 index 0000000000..e8c027dd2b --- /dev/null +++ b/api/async @@ -0,0 +1,8 @@ +#pragma once +#ifndef API_ASYNC_HEADER +#define API_ASYNC_HEADER + +// hmm... +#include "utility/async.hpp" + +#endif diff --git a/api/common b/api/common index ebf7cee857..5ebb70d409 100644 --- a/api/common +++ b/api/common @@ -7,9 +7,9 @@ // 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. @@ -38,25 +38,13 @@ #error "This project needs to be compiled with an ix86-elf compiler" #endif -#ifndef __includeOS__ -#define __includeOS__ -#endif - -/* Unused parameters (necessary for syscall warnings) */ +/* Unused parameters */ #ifdef __GNUC__ # define UNUSED(x) UNUSED_ ## x __attribute__((__unused__)) #else # define UNUSED(x) UNUSED_ ## x #endif -/* Hardware @todo Make this more C++'ish and proper. */ -#define MAX_NICS 4 -#define MAX_DISKS 4 -#define MAX_SERIALS 4 - -/* LIMITS */ -//#define SBRK_MAX 0x10000 - /* BOCHS Break point */ #define BREAK __asm__ volatile("xchg %bx,%bx"); @@ -65,9 +53,47 @@ #include "warn" #include "info" -// From sanos, pdir.h -#define PAGESHIFT 12 -#define BTOP(x) ((unsigned long)(x) >> PAGESHIFT) -#define PAGESIZE 4096 +#include -#endif +/* Define our own termination policy for contract */ +/* violation to generate relevant information about */ +/* the reason for termination. */ +#if defined(OS_TERMINATE_ON_CONTRACT_VIOLATION) + +#include // Needed for std::exit +#include // Needed for std::err +#define OS_STRINGIFY(x) #x + +/* Function used to set termination behaviour */ +inline void expects_and_ensures_termination() { std::exit(EXIT_FAILURE); } + +#undef Expects +#undef Ensures + +#define Expects(cond) \ + do { \ + if (not (cond)) { \ + std::cerr << "OS: Precondition (" OS_STRINGIFY(cond) \ + << ") failed...\n" << "\t...in function: " \ + << __func__ << " @" << __FILE__ << ":" \ + << __LINE__ << '\n'; \ + std::set_terminate(expects_and_ensures_termination); \ + std::terminate(); \ + } \ + } while(0) + +#define Ensures(cond) \ + do { \ + if (not (cond)) { \ + std::cerr << "OS: Postcondition (" OS_STRINGIFY(cond) \ + << ") failed...\n" << "\t...in function: " \ + << __func__ << " @" << __FILE__ << ":" \ + << __LINE__ << '\n'; \ + std::set_terminate(expects_and_ensures_termination); \ + std::terminate(); \ + } \ + } while(0) + +#endif //< defined(OS_TERMINATE_ON_CONTRACT_VIOLATION) + +#endif //< INCLUDEOS_COMMON_HEADER diff --git a/api/fs/common.hpp b/api/fs/common.hpp index c09fb25c96..f01a247630 100644 --- a/api/fs/common.hpp +++ b/api/fs/common.hpp @@ -20,16 +20,80 @@ #define FS_COMMON_HPP #include +#include namespace fs { -typedef std::shared_ptr buffer_t; + typedef std::shared_ptr buffer_t; -// TODO: transform this into a class with a bool operator -using error_t = bool; - -/** @var no_error: Always returns boolean false when used in expressions */ -extern error_t no_error; + struct error_t + { + enum token_t { + NO_ERR = 0, + E_IO, // general I/O error + E_MNT, + + E_NOENT, + E_NOTDIR, + }; + + error_t(token_t tk, const std::string& rsn) + : token_(tk), reason_(rsn) {} + + // error code to string + std::string token() const noexcept; + // show explanation for error + std::string reason() const noexcept { + return reason_; + } + + // returns "description": "reason" + std::string to_string() const noexcept { + return token() + ": " + reason(); + } + + // returns true when it's an error + operator bool () const noexcept { + return token_ != NO_ERR; + } + + private: + token_t token_; + std::string reason_; + }; + + struct Buffer + { + Buffer(error_t e, buffer_t b, size_t l) + : err(e), buffer(b), len(l) {} + + // returns true if this buffer is valid + bool is_valid() const noexcept { + return buffer != nullptr; + } + operator bool () const noexcept { + return is_valid(); + } + + uint8_t* data() { + return buffer.get(); + } + size_t size() const noexcept { + return len; + } + + // create a std::string from the stored buffer and return it + std::string to_string() const noexcept { + return std::string((char*) buffer.get(), size()); + } + + error_t err; + buffer_t buffer; + uint64_t len; + }; + + /** @var no_error: Always returns boolean false when used in expressions */ + extern error_t no_error; } //< namespace fs diff --git a/api/fs/disk.hpp b/api/fs/disk.hpp index cc633ccc38..3c80e62077 100644 --- a/api/fs/disk.hpp +++ b/api/fs/disk.hpp @@ -15,11 +15,43 @@ // See the License for the specific language governing permissions and // limitations under the License. +/** + * /// Create basic FAT disk /// + * + * // create disk from a given disk-device + * auto disk = std::make_shared (device); + * // mount filesystem on auto-detected volume + * disk->mount( + * [disk] (fs::error_t err) { + * if (err) { + * printf("Bad!\n"); + * return; + * } + * // reference to filesystem + * auto& fs = disk->fs(); + * // synchronous stat: + * auto dirent = fs.stat("/file"); + * }); + * + * /// Construct custom filesystem /// + * + * // constructing on MBR means mount on sector 0 + * disk->mount(filesystem_args..., Disk::MBR, + * [disk] { + * printf("Disk mounted!\n"); + * auto& fs = disk->fs(); + * + * auto dirent = fs.stat("/file"); + * }); + * +**/ + #pragma once #ifndef FS_DISK_HPP #define FS_DISK_HPP #include "common.hpp" +#include "filesystem.hpp" #include #include @@ -28,98 +60,100 @@ namespace fs { -class FileSystem; //< FileSystem interface - -template -class Disk { -public: - struct Partition; //< Representation of a disk partition - - /** Callbacks */ - using on_parts_func = std::function&)>; - using on_mount_func = std::function; + class Disk { + public: + struct Partition; + using on_parts_func = std::function&)>; + using on_mount_func = std::function; + using lba_t = uint32_t; - /** Constructor */ - explicit Disk(hw::IDiskDevice&); - - enum partition_t { - MBR = 0, //< Master Boot Record (0) - /** extended partitions (1-4) */ - VBR1, - VBR2, - VBR3, - VBR4, + enum partition_t { + MBR = 0, //< Master Boot Record (0) + VBR1, //> extended partitions (1-4) + VBR2, + VBR3, + VBR4, + }; - INVALID - }; //< enum partition_t - - struct Partition { - explicit Partition(const uint8_t fl, const uint8_t Id, - const uint32_t LBA, const uint32_t sz) noexcept : - flags {fl}, - id {Id}, - lba_begin {LBA}, - sectors {sz} - {} + struct Partition { + explicit Partition(const uint8_t fl, const uint8_t Id, + const uint32_t LBA, const uint32_t sz) noexcept + : flags {fl}, + id {Id}, + lba_begin {LBA}, + sectors {sz} + {} - uint8_t flags; - uint8_t id; - uint32_t lba_begin; - uint32_t sectors; + uint8_t flags; + uint8_t id; + uint32_t lba_begin; + uint32_t sectors; - // true if the partition has boot code / is bootable - bool is_boot() const noexcept - { return flags & 0x1; } + // true if the partition has boot code / is bootable + bool is_boot() const noexcept + { return flags & 0x1; } - // human-readable name of partition id - std::string name() const; + // human-readable name of partition id + std::string name() const; - // logical block address of beginning of partition - uint32_t lba() const - { return lba_begin; } + // logical block address of beginning of partition + uint32_t lba() const + { return lba_begin; } - }; //< struct Partition - - /** Return a reference to the specified filesystem */ - FileSystem& fs() noexcept - { return *filesys; } - - //************** disk functions **************// - - hw::IDiskDevice& dev() noexcept - { return device; } - - // Returns true if the disk has no sectors - bool empty() const noexcept - { return device.size() == 0; } + }; //< struct Partition - // Mounts the first partition detected (MBR -> VBR1-4 -> ext) - void mount(on_mount_func func); + //************** disk functions **************// - // Mount partition @part as the filesystem FS - void mount(partition_t part, on_mount_func func); + // construct a disk with a given disk-device + explicit Disk(hw::IDiskDevice&); + + // returns the device the disk is using + hw::IDiskDevice& dev() noexcept + { return device; } + + // Returns true if the disk has no sectors + bool empty() const noexcept + { return device.size() == 0; } - /** - * Returns a vector of the partitions on a disk - * - * The disk does not need to be mounted beforehand - */ - void partitions(on_parts_func func); + // Mounts the first partition detected (MBR -> VBR1-4 -> ext) + // NOTE: Always detects and instantiates a FAT filesystem + void mount(on_mount_func func); + + // Mounts specified partition + // NOTE: Always detects and instantiates a FAT filesystem + void mount(partition_t part, on_mount_func func); + + // mount custom filesystem on MBR or VBRn + template + void mount(Args&&... args, partition_t part, on_mount_func func) + { + // construct custom filesystem + filesys.reset(new T(args...)); + internal_mount(part, func); + } -private: - hw::IDiskDevice& device; - std::unique_ptr filesys; -}; //< class Disk + // Creates a vector of the partitions on disk (see: on_parts_func) + // Note: The disk does not need to be mounted beforehand + void partitions(on_parts_func func); + + // returns true if a filesystem is mounted + bool fs_mounted() const noexcept + { return (bool) filesys; } + + // Returns a reference to a mounted filesystem + // If no filesystem is mounted, the results are undefined + FileSystem& fs() noexcept + { return *filesys; } + + private: + void internal_mount(partition_t part, on_mount_func func); + + hw::IDiskDevice& device; + std::unique_ptr filesys; + }; //< class Disk -template -inline Disk::Disk(hw::IDiskDevice& dev) : - device {dev} -{ - filesys.reset(new FS(device)); -} + using Disk_ptr = std::shared_ptr; } //< namespace fs -#include "disk.inl" - #endif //< FS_DISK_HPP diff --git a/api/fs/disk.inl b/api/fs/disk.inl deleted file mode 100644 index dd63a571e6..0000000000 --- a/api/fs/disk.inl +++ /dev/null @@ -1,148 +0,0 @@ -// This file is a part of the IncludeOS unikernel - www.includeos.org -// -// Copyright 2015 Oslo and Akershus University College of Applied Sciences -// and Alfred Bratterud -// -// 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. - -#include "mbr.hpp" - -namespace fs { - -template -inline void -Disk::partitions(on_parts_func func) { - /** Read Master Boot Record (sector 0) */ - device.read(0, - [this, func] (hw::IDiskDevice::buffer_t data) - { - std::vector parts; - - if (!data) { - func(true, parts); - return; - } - - // First sector is the Master Boot Record - auto* mbr =(MBR::mbr*) data.get(); - - for (int i {0}; i < 4; ++i) { - // all the partitions are offsets to potential Volume Boot Records - parts.emplace_back( - mbr->part[i].flags, //< flags - mbr->part[i].type, //< id - mbr->part[i].lba_begin, //< LBA - mbr->part[i].sectors); - } - - func(no_error, parts); - }); -} - -template -inline void -Disk::mount(on_mount_func func) -{ - device.read(0, - [this, func] (hw::IDiskDevice::buffer_t data) - { - if (!data) { - // TODO: error-case for unable to read MBR - mount(INVALID, func); - return; - } - - // auto-detect FAT on MBR: - auto* mbr = (MBR::mbr*) data.get(); - MBR::BPB* bpb = mbr->bpb(); - - if (bpb->bytes_per_sector >= 512 - && bpb->fa_tables != 0 - && bpb->signature != 0) // check MBR signature too - { - // we have FAT on MBR (and we are assuming mount FAT) - mount(MBR, func); - return; - } - - // go through partition list - for (int i = 0; i < 4; i++) - { - if (mbr->part[i].type != 0 // 0 is unused partition - && mbr->part[i].lba_begin != 0 // 0 is MBR anyways - && mbr->part[i].sectors != 0) // 0 means no size, so... - { - mount((partition_t) (VBR1 + i), func); - return; - } - } - - // no partition was found (TODO: extended partitions) - mount(INVALID, func); - return; - }); -} - -template -inline void -Disk::mount(partition_t part, on_mount_func func) { - - if (part == INVALID) - { - // Something bad happened maybe in auto-detect - // Either way, no partition was found - func(true); - return; - } - else if (part == MBR) - { - // For the MBR case, all we need to do is mount on sector 0 - fs().mount(0, device.size(), func); - } - else - { - /** - * Otherwise, we will have to read the LBA offset - * of the partition to be mounted - */ - device.read(0, - [this, part, func] (hw::IDiskDevice::buffer_t data) - { - if (!data) { - // TODO: error-case for unable to read MBR - func(true); - return; - } - - auto* mbr = (MBR::mbr*) data.get(); //< Treat data as MBR - auto pint = static_cast(part - 1); //< Treat VBR1 as index 0 etc. - - /** Get LBA from selected partition */ - auto lba_base = mbr->part[pint].lba_begin; - auto lba_size = mbr->part[pint].sectors; - - /** - * Call the filesystems mount function - * with lba_begin as base address - */ - fs().mount(lba_base, lba_size, func); - }); - } -} - -template -std::string Disk::Partition::name() const { - return MBR::id_to_name(id); -} - -} //< namespace fs diff --git a/api/fs/ext4.hpp b/api/fs/ext4.hpp index df5a821261..6ff0b2839c 100644 --- a/api/fs/ext4.hpp +++ b/api/fs/ext4.hpp @@ -32,16 +32,16 @@ namespace fs struct EXT4 : public FileSystem { /** - Blocks 2^32 2^32 2^32 2^32 - Inodes 2^32 2^32 2^32 2^32 - File System Size 4TiB 8TiB 16TiB 256PiB - Blocks Per Block Group 8,192 16,384 32,768 524,288 - Inodes Per Block Group 8,192 16,384 32,768 524,288 - Block Group Size 8MiB 32MiB 128MiB 32GiB - Blocks Per File, Extents 2^32 2^32 2^32 2^32 - Blocks Per File, Block Maps 16,843,020 134,480,396 1,074,791,436 4,398,314,962,956 - File Size, Extents 4TiB 8TiB 16TiB 256TiB - File Size, Block Maps 16GiB 256GiB 4TiB 256PiB + Blocks 2^32 2^32 2^32 2^32 + Inodes 2^32 2^32 2^32 2^32 + File System Size 4TiB 8TiB 16TiB 256PiB + Blocks Per Block Group 8,192 16,384 32,768 524,288 + Inodes Per Block Group 8,192 16,384 32,768 524,288 + Block Group Size 8MiB 32MiB 128MiB 32GiB + Blocks Per File, Extents 2^32 2^32 2^32 2^32 + Blocks Per File, Block Maps 16,843,020 134,480,396 1,074,791,436 4,398,314,962,956 + File Size, Extents 4TiB 8TiB 16TiB 256TiB + File Size, Block Maps 16GiB 256GiB 4TiB 256PiB **/ // 0 = Mount MBR @@ -49,12 +49,12 @@ namespace fs virtual void mount(uint64_t lba, uint64_t size, on_mount_func on_mount) override; // path is a path in the mounted filesystem - virtual void ls(const std::string& path, on_ls_func) override; - virtual error_t ls(const std::string& path, dirvec_t e) override; + virtual void ls(const std::string& path, on_ls_func) override; + virtual List ls(const std::string& path) override; // read an entire file into a buffer, then call on_read - virtual void readFile(const std::string&, on_read_func) override; - virtual void readFile(const Dirent& ent, on_read_func) override; + virtual void readFile(const std::string&, on_read_func) override; + virtual Buffer readFile(const std::string&) override; /** Read @n bytes from file pointed by @entry starting at position @pos */ virtual void read(const Dirent&, uint64_t pos, uint64_t n, on_read_func) override; @@ -103,15 +103,15 @@ namespace fs uint16_t magic; // Magic signature, 0xEF53 // File system state. Valid values are: - // 0x0001 Cleanly umounted - // 0x0002 Errors detected - // 0x0004 Orphans being recovered + // 0x0001 Cleanly umounted + // 0x0002 Errors detected + // 0x0004 Orphans being recovered uint16_t state; // Behaviour when detecting errors. One of: - // 1 Continue - // 2 Remount read-only - // 3 Panic + // 1 Continue + // 2 Remount read-only + // 3 Panic uint16_t errors; uint16_t minor_rev_level; // Minor revision level @@ -119,16 +119,16 @@ namespace fs uint32_t checkinterval; // Maximum time between checks, in seconds // OS. One of: - // 0 Linux - // 1 Hurd - // 2 Masix - // 3 FreeBSD - // 4 Lites + // 0 Linux + // 1 Hurd + // 2 Masix + // 3 FreeBSD + // 4 Lites uint32_t creator_os; // Revision level. One of: - // 0 Original format - // 1 v2 format w/ dynamic inode sizes + // 0 Original format + // 1 v2 format w/ dynamic inode sizes uint32_t rev_level; uint16_t def_resuid; // Default uid for reserved blocks @@ -155,54 +155,54 @@ namespace fs // Compatible feature set flags. // Kernel can still read/write this fs even if it doesn't // understand a flag; fsck should not do that. Any of: - // 0x1 Directory preallocation (COMPAT_DIR_PREALLOC). - // 0x2 "imagic inodes". Not clear from the code what this does (COMPAT_IMAGIC_INODES). - // 0x4 Has a journal (COMPAT_HAS_JOURNAL). - // 0x8 Supports extended attributes (COMPAT_EXT_ATTR). - // 0x10 Has reserved GDT blocks for filesystem expansion (COMPAT_RESIZE_INODE). - // 0x20 Has directory indices (COMPAT_DIR_INDEX). - // 0x40 "Lazy BG". Not in Linux kernel, seems to have been for uninitialized block groups? (COMPAT_LAZY_BG) - // 0x80 "Exclude inode". Not used. (COMPAT_EXCLUDE_INODE). - // 0x100 "Exclude bitmap". Seems to be used to indicate the presence of snapshot-related exclude bitmaps? Not defined in kernel or used in e2fsprogs (COMPAT_EXCLUDE_BITMAP). - // 0x200 Sparse Super Block, v2. If this flag is set, the SB field s_backup_bgs points to the two block groups that contain backup superblocks (COMPAT_SPARSE_SUPER2). + // 0x1 Directory preallocation (COMPAT_DIR_PREALLOC). + // 0x2 "imagic inodes". Not clear from the code what this does (COMPAT_IMAGIC_INODES). + // 0x4 Has a journal (COMPAT_HAS_JOURNAL). + // 0x8 Supports extended attributes (COMPAT_EXT_ATTR). + // 0x10 Has reserved GDT blocks for filesystem expansion (COMPAT_RESIZE_INODE). + // 0x20 Has directory indices (COMPAT_DIR_INDEX). + // 0x40 "Lazy BG". Not in Linux kernel, seems to have been for uninitialized block groups? (COMPAT_LAZY_BG) + // 0x80 "Exclude inode". Not used. (COMPAT_EXCLUDE_INODE). + // 0x100 "Exclude bitmap". Seems to be used to indicate the presence of snapshot-related exclude bitmaps? Not defined in kernel or used in e2fsprogs (COMPAT_EXCLUDE_BITMAP). + // 0x200 Sparse Super Block, v2. If this flag is set, the SB field s_backup_bgs points to the two block groups that contain backup superblocks (COMPAT_SPARSE_SUPER2). uint32_t feature_compat; // Incompatible feature set. If the kernel or fsck doesn't // understand one of these bits, it should stop. Any of: - // 0x1 Compression (INCOMPAT_COMPRESSION). - // 0x2 Directory entries record the file type. See ext4_dir_entry_2 below (INCOMPAT_FILETYPE). - // 0x4 Filesystem needs recovery (INCOMPAT_RECOVER). - // 0x8 Filesystem has a separate journal device (INCOMPAT_JOURNAL_DEV). - // 0x10 Meta block groups. See the earlier discussion of this feature (INCOMPAT_META_BG). - // 0x40 Files in this filesystem use extents (INCOMPAT_EXTENTS). - // 0x80 Enable a filesystem size of 2^64 blocks (INCOMPAT_64BIT). - // 0x100 Multiple mount protection. Not implemented (INCOMPAT_MMP). - // 0x200 Flexible block groups. See the earlier discussion of this feature (INCOMPAT_FLEX_BG). - // 0x400 Inodes can be used for large extended attributes (INCOMPAT_EA_INODE). (Not implemented?) - // 0x1000 Data in directory entry (INCOMPAT_DIRDATA). (Not implemented?) - // 0x2000 Metadata checksum seed is stored in the superblock. This feature enables the administrator to change the UUID of a metadata_csum filesystem while the filesystem is mounted; without it, the checksum definition requires all metadata blocks to be rewritten (INCOMPAT_CSUM_SEED). - // 0x4000 Large directory >2GB or 3-level htree (INCOMPAT_LARGEDIR). - // 0x8000 Data in inode (INCOMPAT_INLINE_DATA). - // 0x10000 Encrypted inodes are present on the filesystem. (INCOMPAT_ENCRYPT). + // 0x1 Compression (INCOMPAT_COMPRESSION). + // 0x2 Directory entries record the file type. See ext4_dir_entry_2 below (INCOMPAT_FILETYPE). + // 0x4 Filesystem needs recovery (INCOMPAT_RECOVER). + // 0x8 Filesystem has a separate journal device (INCOMPAT_JOURNAL_DEV). + // 0x10 Meta block groups. See the earlier discussion of this feature (INCOMPAT_META_BG). + // 0x40 Files in this filesystem use extents (INCOMPAT_EXTENTS). + // 0x80 Enable a filesystem size of 2^64 blocks (INCOMPAT_64BIT). + // 0x100 Multiple mount protection. Not implemented (INCOMPAT_MMP). + // 0x200 Flexible block groups. See the earlier discussion of this feature (INCOMPAT_FLEX_BG). + // 0x400 Inodes can be used for large extended attributes (INCOMPAT_EA_INODE). (Not implemented?) + // 0x1000 Data in directory entry (INCOMPAT_DIRDATA). (Not implemented?) + // 0x2000 Metadata checksum seed is stored in the superblock. This feature enables the administrator to change the UUID of a metadata_csum filesystem while the filesystem is mounted; without it, the checksum definition requires all metadata blocks to be rewritten (INCOMPAT_CSUM_SEED). + // 0x4000 Large directory >2GB or 3-level htree (INCOMPAT_LARGEDIR). + // 0x8000 Data in inode (INCOMPAT_INLINE_DATA). + // 0x10000 Encrypted inodes are present on the filesystem. (INCOMPAT_ENCRYPT). uint32_t feature_incompat; // Readonly-compatible feature set. If the kernel doesn't // understand one of these bits, it can still mount read-only. // Any of: - //0x1 Sparse superblocks. See the earlier discussion of this feature (RO_COMPAT_SPARSE_SUPER). - //0x2 This filesystem has been used to store a file greater than 2GiB (RO_COMPAT_LARGE_FILE). - //0x4 Not used in kernel or e2fsprogs (RO_COMPAT_BTREE_DIR). - //0x8 This filesystem has files whose sizes are represented in units of logical blocks, not 512-byte sectors. This implies a very large file indeed! (RO_COMPAT_HUGE_FILE) - //0x10 Group descriptors have checksums. In addition to detecting corruption, this is useful for lazy formatting with uninitialized groups (RO_COMPAT_GDT_CSUM). - //0x20 Indicates that the old ext3 32,000 subdirectory limit no longer applies (RO_COMPAT_DIR_NLINK). - //0x40 Indicates that large inodes exist on this filesystem (RO_COMPAT_EXTRA_ISIZE). - //0x80 This filesystem has a snapshot (RO_COMPAT_HAS_SNAPSHOT). - //0x100 Quota (RO_COMPAT_QUOTA). - //0x200 This filesystem supports "bigalloc", which means that file extents are tracked in units of clusters (of blocks) instead of blocks (RO_COMPAT_BIGALLOC). - //0x400 This filesystem supports metadata checksumming. (RO_COMPAT_METADATA_CSUM; implies RO_COMPAT_GDT_CSUM, though GDT_CSUM must not be set) - //0x800 Filesystem supports replicas. This feature is neither in the kernel nor e2fsprogs. (RO_COMPAT_REPLICA) - //0x1000 Read-only filesystem image; the kernel will not mount this image read-write and most tools will refuse to write to the image. (RO_COMPAT_READONLY) - //0x2000 Filesystem tracks project quotas. (RO_COMPAT_PROJECT) + //0x1 Sparse superblocks. See the earlier discussion of this feature (RO_COMPAT_SPARSE_SUPER). + //0x2 This filesystem has been used to store a file greater than 2GiB (RO_COMPAT_LARGE_FILE). + //0x4 Not used in kernel or e2fsprogs (RO_COMPAT_BTREE_DIR). + //0x8 This filesystem has files whose sizes are represented in units of logical blocks, not 512-byte sectors. This implies a very large file indeed! (RO_COMPAT_HUGE_FILE) + //0x10 Group descriptors have checksums. In addition to detecting corruption, this is useful for lazy formatting with uninitialized groups (RO_COMPAT_GDT_CSUM). + //0x20 Indicates that the old ext3 32,000 subdirectory limit no longer applies (RO_COMPAT_DIR_NLINK). + //0x40 Indicates that large inodes exist on this filesystem (RO_COMPAT_EXTRA_ISIZE). + //0x80 This filesystem has a snapshot (RO_COMPAT_HAS_SNAPSHOT). + //0x100 Quota (RO_COMPAT_QUOTA). + //0x200 This filesystem supports "bigalloc", which means that file extents are tracked in units of clusters (of blocks) instead of blocks (RO_COMPAT_BIGALLOC). + //0x400 This filesystem supports metadata checksumming. (RO_COMPAT_METADATA_CSUM; implies RO_COMPAT_GDT_CSUM, though GDT_CSUM must not be set) + //0x800 Filesystem supports replicas. This feature is neither in the kernel nor e2fsprogs. (RO_COMPAT_REPLICA) + //0x1000 Read-only filesystem image; the kernel will not mount this image read-write and most tools will refuse to write to the image. (RO_COMPAT_READONLY) + //0x2000 Filesystem tracks project quotas. (RO_COMPAT_PROJECT) uint32_t feature_ro_compat; uint8_t uuid[16]; // 128-bit UUID for volume @@ -225,19 +225,19 @@ namespace fs // Journaling support valid if EXT4_FEATURE_COMPAT_HAS_JOURNAL set uint8_t journal_uuid[16]; // UUID of journal superblock - uint32_t journal_inum; // inode number of journal file. + uint32_t journal_inum; // inode number of journal file. uint32_t journal_dev; // Device number of journal file, if the external journal feature flag is set uint32_t last_orphan; // Start of list of orphaned inodes to delete uint32_t hash_seed[4]; // HTREE hash seed // Default hash algorithm to use for directory hashes. One of: - // 0x0 Legacy. - // 0x1 Half MD4. - // 0x2 Tea. - // 0x3 Legacy, unsigned. - // 0x4 Half MD4, unsigned. - // 0x5 Tea, unsigned. + // 0x0 Legacy. + // 0x1 Half MD4. + // 0x2 Tea. + // 0x3 Legacy, unsigned. + // 0x4 Half MD4, unsigned. + // 0x5 Tea, unsigned. uint8_t def_hash_version; // If this value is 0 or EXT3_JNL_BACKUP_BLOCKS (1), then the s_jnl_blocks field contains a duplicate copy of the inode's i_block[] array and i_size @@ -246,18 +246,18 @@ namespace fs uint16_t desc_size; // Default mount options. Any of: - // 0x001 Print debugging info upon (re)mount. (EXT4_DEFM_DEBUG) - // 0x002 New files take the gid of the containing directory (instead of the fsgid of the current process). (EXT4_DEFM_BSDGROUPS) - // 0x004 Support userspace-provided extended attributes. (EXT4_DEFM_XATTR_USER) - // 0x008 Support POSIX access control lists (ACLs). (EXT4_DEFM_ACL) - // 0x010 Do not support 32-bit UIDs. (EXT4_DEFM_UID16) - // 0x020 All data and metadata are commited to the journal. (EXT4_DEFM_JMODE_DATA) - // 0x040 All data are flushed to the disk before metadata are committed to the journal. (EXT4_DEFM_JMODE_ORDERED) - // 0x060 Data ordering is not preserved; data may be written after the metadata has been written. (EXT4_DEFM_JMODE_WBACK) - // 0x100 Disable write flushes. (EXT4_DEFM_NOBARRIER) - // 0x200 Track which blocks in a filesystem are metadata and therefore should not be used as data blocks. This option will be enabled by default on 3.18, hopefully. (EXT4_DEFM_BLOCK_VALIDITY) - // 0x400 Enable DISCARD support, where the storage device is told about blocks becoming unused. (EXT4_DEFM_DISCARD) - // 0x800 Disable delayed allocation. (EXT4_DEFM_NODELALLOC) + // 0x001 Print debugging info upon (re)mount. (EXT4_DEFM_DEBUG) + // 0x002 New files take the gid of the containing directory (instead of the fsgid of the current process). (EXT4_DEFM_BSDGROUPS) + // 0x004 Support userspace-provided extended attributes. (EXT4_DEFM_XATTR_USER) + // 0x008 Support POSIX access control lists (ACLs). (EXT4_DEFM_ACL) + // 0x010 Do not support 32-bit UIDs. (EXT4_DEFM_UID16) + // 0x020 All data and metadata are commited to the journal. (EXT4_DEFM_JMODE_DATA) + // 0x040 All data are flushed to the disk before metadata are committed to the journal. (EXT4_DEFM_JMODE_ORDERED) + // 0x060 Data ordering is not preserved; data may be written after the metadata has been written. (EXT4_DEFM_JMODE_WBACK) + // 0x100 Disable write flushes. (EXT4_DEFM_NOBARRIER) + // 0x200 Track which blocks in a filesystem are metadata and therefore should not be used as data blocks. This option will be enabled by default on 3.18, hopefully. (EXT4_DEFM_BLOCK_VALIDITY) + // 0x400 Enable DISCARD support, where the storage device is told about blocks becoming unused. (EXT4_DEFM_DISCARD) + // 0x800 Disable delayed allocation. (EXT4_DEFM_NODELALLOC) uint32_t default_mount_opts; // First metablock block group, if the meta_bg feature is enabled @@ -275,9 +275,9 @@ namespace fs uint16_t want_extra_isize; // New inodes should reserve # bytes // Miscellaneous flags. Any of: - // 0x01 Signed directory hash in use. - // 0x02 Unsigned directory hash in use. - // 0x04 To test development code. + // 0x01 Signed directory hash in use. + // 0x02 Unsigned directory hash in use. + // 0x04 To test development code. uint32_t flags; // RAID stride. This is the number of logical blocks read from or written to the disk before moving to the next disk. This affects the placement of filesystem metadata, which will hopefully make RAID storage faster @@ -321,10 +321,10 @@ namespace fs uint32_t backup_bgs[2]; // Block groups containing superblock backups (if sparse_super2) // Encryption algorithms in use. There can be up to four algorithms in use at any time; valid algorithm codes are given below: - // 0 Invalid algorithm (ENCRYPTION_MODE_INVALID). - // 1 256-bit AES in XTS mode (ENCRYPTION_MODE_AES_256_XTS). - // 2 256-bit AES in GCM mode (ENCRYPTION_MODE_AES_256_GCM). - // 3 256-bit AES in CBC mode (ENCRYPTION_MODE_AES_256_CBC). + // 0 Invalid algorithm (ENCRYPTION_MODE_INVALID). + // 1 256-bit AES in XTS mode (ENCRYPTION_MODE_AES_256_XTS). + // 2 256-bit AES in GCM mode (ENCRYPTION_MODE_AES_256_GCM). + // 3 256-bit AES in CBC mode (ENCRYPTION_MODE_AES_256_CBC). uint8_t encrypt_algos[4]; // Salt for the string2key algorithm for encryption. uint8_t encrypt_pw_salt[16]; @@ -351,9 +351,9 @@ namespace fs uint16_t free_inodes_count_lo; // Lower 16-bits of free inode count uint16_t used_dirs_count_lo; // Lower 16-bits of directory count // Block group flags. Any of: - // 0x1 inode table and bitmap are not initialized (EXT4_BG_INODE_UNINIT). - // 0x2 block bitmap is not initialized (EXT4_BG_BLOCK_UNINIT). - // 0x4 inode table is zeroed (EXT4_BG_INODE_ZEROED). + // 0x1 inode table and bitmap are not initialized (EXT4_BG_INODE_UNINIT). + // 0x2 block bitmap is not initialized (EXT4_BG_BLOCK_UNINIT). + // 0x4 inode table is zeroed (EXT4_BG_INODE_ZEROED). uint16_t flags; uint32_t exclude_bitmap_lo; // Lower 32-bits of location of snapshot exclusion bitmap @@ -386,26 +386,26 @@ namespace fs struct inode_table { // File mode. Any of: - // 0x1 S_IXOTH (Others may execute) - // 0x2 S_IWOTH (Others may write) - // 0x4 S_IROTH (Others may read) - // 0x8 S_IXGRP (Group members may execute) - // 0x10 S_IWGRP (Group members may write) - // 0x20 S_IRGRP (Group members may read) - // 0x40 S_IXUSR (Owner may execute) - // 0x80 S_IWUSR (Owner may write) - // 0x100 S_IRUSR (Owner may read) - // 0x200 S_ISVTX (Sticky bit) - // 0x400 S_ISGID (Set GID) - // 0x800 S_ISUID (Set UID) + // 0x1 S_IXOTH (Others may execute) + // 0x2 S_IWOTH (Others may write) + // 0x4 S_IROTH (Others may read) + // 0x8 S_IXGRP (Group members may execute) + // 0x10 S_IWGRP (Group members may write) + // 0x20 S_IRGRP (Group members may read) + // 0x40 S_IXUSR (Owner may execute) + // 0x80 S_IWUSR (Owner may write) + // 0x100 S_IRUSR (Owner may read) + // 0x200 S_ISVTX (Sticky bit) + // 0x400 S_ISGID (Set GID) + // 0x800 S_ISUID (Set UID) // These are mutually-exclusive file types: - // 0x1000 S_IFIFO (FIFO) - // 0x2000 S_IFCHR (Character device) - // 0x4000 S_IFDIR (Directory) - // 0x6000 S_IFBLK (Block device) - // 0x8000 S_IFREG (Regular file) - // 0xA000 S_IFLNK (Symbolic link) - // 0xC000 S_IFSOCK (Socket) + // 0x1000 S_IFIFO (FIFO) + // 0x2000 S_IFCHR (Character device) + // 0x4000 S_IFDIR (Directory) + // 0x6000 S_IFBLK (Block device) + // 0x8000 S_IFREG (Regular file) + // 0xA000 S_IFLNK (Symbolic link) + // 0xC000 S_IFSOCK (Socket) uint16_t mode; uint16_t uid; // Owner-ID (lower 16) @@ -419,37 +419,37 @@ namespace fs uint32_t blocks_lo; // "Block" count (lower 32) // Inode flags. Any of: - // 0x1 This file requires secure deletion (EXT4_SECRM_FL). (not implemented) - // 0x2 This file should be preserved, should undeletion be desired (EXT4_UNRM_FL). (not implemented) - // 0x4 File is compressed (EXT4_COMPR_FL). (not really implemented) - // 0x8 All writes to the file must be synchronous (EXT4_SYNC_FL). - // 0x10 File is immutable (EXT4_IMMUTABLE_FL). - // 0x20 File can only be appended (EXT4_APPEND_FL). - // 0x40 The dump(1) utility should not dump this file (EXT4_NODUMP_FL). - // 0x80 Do not update access time (EXT4_NOATIME_FL). - // 0x100 Dirty compressed file (EXT4_DIRTY_FL). (not used) - // 0x200 File has one or more compressed clusters (EXT4_COMPRBLK_FL). (not used) - // 0x400 Do not compress file (EXT4_NOCOMPR_FL). (not used) - // 0x800 Encrypted inode (EXT4_ENCRYPT_FL). This bit value previously was EXT4_ECOMPR_FL (compression error), which was never used. - // 0x1000 Directory has hashed indexes (EXT4_INDEX_FL). - // 0x2000 AFS magic directory (EXT4_IMAGIC_FL). - // 0x4000 File data must always be written through the journal (EXT4_JOURNAL_DATA_FL). - // 0x8000 File tail should not be merged (EXT4_NOTAIL_FL). (not used by ext4) - // 0x10000 All directory entry data should be written synchronously (see dirsync) (EXT4_DIRSYNC_FL). - // 0x20000 Top of directory hierarchy (EXT4_TOPDIR_FL). - // 0x40000 This is a huge file (EXT4_HUGE_FILE_FL). - // 0x80000 Inode uses extents (EXT4_EXTENTS_FL). - // 0x200000 Inode used for a large extended attribute (EXT4_EA_INODE_FL). - // 0x400000 This file has blocks allocated past EOF (EXT4_EOFBLOCKS_FL). (deprecated) - // 0x01000000 Inode is a snapshot (EXT4_SNAPFILE_FL). (not in mainline) - // 0x04000000 Snapshot is being deleted (EXT4_SNAPFILE_DELETED_FL). (not in mainline) - // 0x08000000 Snapshot shrink has completed (EXT4_SNAPFILE_SHRUNK_FL). (not in mainline) - // 0x10000000 Inode has inline data (EXT4_INLINE_DATA_FL). - // 0x20000000 Create children with the same project ID (EXT4_PROJINHERIT_FL). - // 0x80000000 Reserved for ext4 library (EXT4_RESERVED_FL). + // 0x1 This file requires secure deletion (EXT4_SECRM_FL). (not implemented) + // 0x2 This file should be preserved, should undeletion be desired (EXT4_UNRM_FL). (not implemented) + // 0x4 File is compressed (EXT4_COMPR_FL). (not really implemented) + // 0x8 All writes to the file must be synchronous (EXT4_SYNC_FL). + // 0x10 File is immutable (EXT4_IMMUTABLE_FL). + // 0x20 File can only be appended (EXT4_APPEND_FL). + // 0x40 The dump(1) utility should not dump this file (EXT4_NODUMP_FL). + // 0x80 Do not update access time (EXT4_NOATIME_FL). + // 0x100 Dirty compressed file (EXT4_DIRTY_FL). (not used) + // 0x200 File has one or more compressed clusters (EXT4_COMPRBLK_FL). (not used) + // 0x400 Do not compress file (EXT4_NOCOMPR_FL). (not used) + // 0x800 Encrypted inode (EXT4_ENCRYPT_FL). This bit value previously was EXT4_ECOMPR_FL (compression error), which was never used. + // 0x1000 Directory has hashed indexes (EXT4_INDEX_FL). + // 0x2000 AFS magic directory (EXT4_IMAGIC_FL). + // 0x4000 File data must always be written through the journal (EXT4_JOURNAL_DATA_FL). + // 0x8000 File tail should not be merged (EXT4_NOTAIL_FL). (not used by ext4) + // 0x10000 All directory entry data should be written synchronously (see dirsync) (EXT4_DIRSYNC_FL). + // 0x20000 Top of directory hierarchy (EXT4_TOPDIR_FL). + // 0x40000 This is a huge file (EXT4_HUGE_FILE_FL). + // 0x80000 Inode uses extents (EXT4_EXTENTS_FL). + // 0x200000 Inode used for a large extended attribute (EXT4_EA_INODE_FL). + // 0x400000 This file has blocks allocated past EOF (EXT4_EOFBLOCKS_FL). (deprecated) + // 0x01000000 Inode is a snapshot (EXT4_SNAPFILE_FL). (not in mainline) + // 0x04000000 Snapshot is being deleted (EXT4_SNAPFILE_DELETED_FL). (not in mainline) + // 0x08000000 Snapshot shrink has completed (EXT4_SNAPFILE_SHRUNK_FL). (not in mainline) + // 0x10000000 Inode has inline data (EXT4_INLINE_DATA_FL). + // 0x20000000 Create children with the same project ID (EXT4_PROJINHERIT_FL). + // 0x80000000 Reserved for ext4 library (EXT4_RESERVED_FL). // Aggregate flags: - // 0x4BDFFF User-visible flags. - // 0x4B80FF User-modifiable flags. Note that while EXT4_JOURNAL_DATA_FL and EXT4_EXTENTS_FL can be set with setattr, they are not in the kernel's EXT4_FL_USER_MODIFIABLE mask, since it needs to handle the setting of these flags in a special manner and they are masked out of the set of flags that are saved directly to i_flags. + // 0x4BDFFF User-visible flags. + // 0x4B80FF User-modifiable flags. Note that while EXT4_JOURNAL_DATA_FL and EXT4_EXTENTS_FL can be set with setattr, they are not in the kernel's EXT4_FL_USER_MODIFIABLE mask, since it needs to handle the setting of these flags in a special manner and they are masked out of the set of flags that are saved directly to i_flags. uint32_t flags; union // linux1, hurd1, masix1 diff --git a/api/fs/fat.hpp b/api/fs/fat.hpp index 16c1d336e2..981a473824 100644 --- a/api/fs/fat.hpp +++ b/api/fs/fat.hpp @@ -35,12 +35,14 @@ namespace fs virtual void mount(uint64_t lba, uint64_t size, on_mount_func on_mount) override; // path is a path in the mounted filesystem - virtual void ls (const std::string& path, on_ls_func) override; - virtual error_t ls(const std::string& path, dirvec_t) override; + virtual void ls (const std::string& path, on_ls_func) override; + virtual void ls (const Dirent& entry, on_ls_func) override; + virtual List ls(const std::string& path) override; + virtual List ls(const Dirent&) override; // read an entire file into a buffer, then call on_read - virtual void readFile(const std::string&, on_read_func) override; - virtual void readFile(const Dirent& ent, on_read_func) override; + virtual void readFile(const std::string&, on_read_func) override; + virtual Buffer readFile(const std::string&) override; /** Read @n bytes from file pointed by @entry starting at position @pos */ virtual void read(const Dirent&, uint64_t pos, uint64_t n, on_read_func) override; @@ -54,14 +56,14 @@ namespace fs virtual std::string name() const override { switch (this->fat_type) - { - case T_FAT12: + { + case T_FAT12: return "FAT12"; - case T_FAT16: + case T_FAT16: return "FAT16"; - case T_FAT32: + case T_FAT32: return "FAT32"; - } + } return "Invalid fat type"; } /// ----------------------------------------------------- /// @@ -121,7 +123,7 @@ namespace fs uint32_t size() const { - return filesize; + return filesize; } } __attribute__((packed)); @@ -151,9 +153,9 @@ namespace fs } __attribute__((packed)); // helper functions - uint32_t cl_to_sector(uint32_t cl) + uint32_t cl_to_sector(uint32_t const cl) { - if (cl == 0) + if (cl <= 2) return lba_base + data_index + (this->root_cluster - 2) * sectors_per_cluster - this->root_dir_sectors; else return lba_base + data_index + (cl - 2) * sectors_per_cluster; @@ -162,16 +164,16 @@ namespace fs uint16_t cl_to_entry_offset(uint32_t cl) { if (fat_type == T_FAT16) - return (cl * 2) % sector_size; + return (cl * 2) % sector_size; else // T_FAT32 - return (cl * 4) % sector_size; + return (cl * 4) % sector_size; } uint16_t cl_to_entry_sector(uint32_t cl) { if (fat_type == T_FAT16) - return reserved + (cl * 2 / sector_size); + return reserved + (cl * 2 / sector_size); else // T_FAT32 - return reserved + (cl * 4 / sector_size); + return reserved + (cl * 4 / sector_size); } // initialize filesystem by providing base sector @@ -179,15 +181,15 @@ namespace fs // return a list of entries from directory entries at @sector typedef std::function on_internal_ls_func; void int_ls(uint32_t sector, dirvec_t, on_internal_ls_func); - bool int_dirent(uint32_t sector, const void* data, dirvec_t); + bool int_dirent(uint32_t sector, const void* data, dirvector&); // tree traversal typedef std::function cluster_func; // async tree traversal void traverse(std::shared_ptr path, cluster_func callback); // sync version - error_t traverse(Path path, dirvec_t); - error_t int_ls(uint32_t sector, dirvec_t); + error_t traverse(Path path, dirvector&); + error_t int_ls(uint32_t sector, dirvector&); // device we can read and write sectors to hw::IDiskDevice& device; diff --git a/api/fs/filesystem.hpp b/api/fs/filesystem.hpp index 05702bcb9f..595de33c66 100644 --- a/api/fs/filesystem.hpp +++ b/api/fs/filesystem.hpp @@ -27,109 +27,110 @@ #include namespace fs { - -class FileSystem { -public: - struct Dirent; //< Generic structure for directory entries - using dirvector = std::vector; - using dirvec_t = std::shared_ptr; - using buffer_t = std::shared_ptr; + class FileSystem { + public: + struct Dirent; //< Generic structure for directory entries - using on_mount_func = std::function; - using on_ls_func = std::function; - using on_read_func = std::function; - using on_stat_func = std::function; + using dirvector = std::vector; + using dirvec_t = std::shared_ptr; + using buffer_t = std::shared_ptr; - struct Buffer - { - error_t err; - buffer_t buffer; - uint64_t len; - - Buffer(error_t e, buffer_t b, size_t l) - : err(e), buffer(b), len(l) {} - }; + using on_mount_func = std::function; + using on_ls_func = std::function; + using on_read_func = std::function; + using on_stat_func = std::function; - enum Enttype { - FILE, - DIR, - /** FAT puts disk labels in the root directory, hence: */ - VOLUME_ID, - SYM_LINK, + enum Enttype { + FILE, + DIR, + /** FAT puts disk labels in the root directory, hence: */ + VOLUME_ID, + SYM_LINK, - INVALID_ENTITY - }; //< enum Enttype + INVALID_ENTITY + }; //< enum Enttype - /** Generic structure for directory entries */ - struct Dirent { - /** Default constructor */ - explicit Dirent(const Enttype t = INVALID_ENTITY, const std::string& n = "", - const uint64_t blk = 0U, const uint64_t pr = 0U, - const uint64_t sz = 0U, const uint32_t attr = 0U) : - ftype {t}, - fname {n}, - block {blk}, - parent {pr}, - size {sz}, - attrib {attr}, - timestamp {0} - {} + /** Generic structure for directory entries */ + struct Dirent { + /** Default constructor */ + explicit Dirent(const Enttype t = INVALID_ENTITY, const std::string& n = "", + const uint64_t blk = 0U, const uint64_t pr = 0U, + const uint64_t sz = 0U, const uint32_t attr = 0U) : + ftype {t}, + fname {n}, + block {blk}, + parent {pr}, + size_ {sz}, + attrib {attr}, + timestamp {0} + {} - Enttype ftype; - std::string fname; - uint64_t block; - uint64_t parent; //< Parent's block# - uint64_t size; - uint32_t attrib; - int64_t timestamp; + Enttype type() const noexcept + { return ftype; } - Enttype type() const noexcept - { return ftype; } + // true if this dirent is valid + // if not, it means don't read any values from the Dirent as they are not + bool is_valid() const + { return ftype != INVALID_ENTITY; } - // true if this dirent is valid - // if not, it means don't read any values from the Dirent as they are not - bool is_valid() const - { return ftype != INVALID_ENTITY; } + // most common types + bool is_file() const noexcept + { return ftype == FILE; } + bool is_dir() const noexcept + { return ftype == DIR; } - // most common types - bool is_file() const noexcept - { return ftype == FILE; } - bool is_dir() const noexcept - { return ftype == DIR; } + // the entrys name + const std::string& name() const noexcept + { return fname; } - // the entrys name - const std::string& name() const noexcept - { return fname; } - - // type converted to human-readable string - std::string type_string() const { - switch (ftype) { - case FILE: - return "File"; - case DIR: - return "Directory"; - case VOLUME_ID: - return "Volume ID"; + // type converted to human-readable string + std::string type_string() const { + switch (ftype) { + case FILE: + return "File"; + case DIR: + return "Directory"; + case VOLUME_ID: + return "Volume ID"; - case INVALID_ENTITY: - return "Invalid entity"; - default: - return "Unknown type"; - } //< switch (type) - } - }; //< struct Dirent - - /** Mount this filesystem with LBA at @base_sector */ + case INVALID_ENTITY: + return "Invalid entity"; + default: + return "Unknown type"; + } //< switch (type) + } + + uint64_t size() const noexcept { + return size_; + } + + Enttype ftype; + std::string fname; + uint64_t block; + uint64_t parent; //< Parent's block# + uint64_t size_; + uint32_t attrib; + int64_t timestamp; + }; //< struct Dirent + + struct List { + error_t error; + dirvec_t entries; + }; + + /** Mount this filesystem with LBA at @base_sector */ virtual void mount(uint64_t lba, uint64_t size, on_mount_func on_mount) = 0; /** @param path: Path in the mounted filesystem */ - virtual void ls(const std::string& path, on_ls_func) = 0; - virtual error_t ls(const std::string& path, dirvec_t e) = 0; + virtual void ls(const std::string& path, on_ls_func) = 0; + virtual void ls(const Dirent& entry, on_ls_func) = 0; + virtual List ls(const std::string& path) = 0; + virtual List ls(const Dirent&) = 0; /** Read an entire file into a buffer, then call on_read */ - virtual void readFile(const std::string&, on_read_func) = 0; - virtual void readFile(const Dirent& ent, on_read_func) = 0; + virtual void readFile(const std::string&, on_read_func) = 0; + virtual Buffer readFile(const std::string&) = 0; /** Read @n bytes from direntry from position @pos */ virtual void read(const Dirent&, uint64_t pos, uint64_t n, on_read_func) = 0; @@ -144,14 +145,16 @@ class FileSystem { /** Default destructor */ virtual ~FileSystem() noexcept = default; -}; //< class FileSystem - -// simplify initializing shared vector -inline FileSystem::dirvec_t new_shared_vector() -{ - return std::make_shared (); -} + }; //< class FileSystem + // simplify initializing shared vector + inline FileSystem::dirvec_t new_shared_vector() + { + return std::make_shared (); + } + + using Dirent = FileSystem::Dirent; + } //< namespace fs #endif //< FS_FILESYSTEM_HPP diff --git a/api/fs/mbr.hpp b/api/fs/mbr.hpp index d36d539544..f888162e0d 100644 --- a/api/fs/mbr.hpp +++ b/api/fs/mbr.hpp @@ -24,53 +24,53 @@ namespace fs { -struct MBR { - static constexpr int PARTITIONS {4}; + struct MBR { + static constexpr int PARTITIONS {4}; - struct partition { - uint8_t flags; - uint8_t CHS_BEG[3]; - uint8_t type; - uint8_t CHS_END[3]; - uint32_t lba_begin; - uint32_t sectors; - } __attribute__((packed)); + struct partition { + uint8_t flags; + uint8_t CHS_BEG[3]; + uint8_t type; + uint8_t CHS_END[3]; + uint32_t lba_begin; + uint32_t sectors; + } __attribute__((packed)); - /** Legacy BIOS Parameter Block */ - struct BPB { - uint16_t bytes_per_sector; - uint8_t sectors_per_cluster; - uint16_t reserved_sectors; - uint8_t fa_tables; - uint16_t root_entries; - uint16_t small_sectors; - uint8_t media_type; // 0xF8 == hard drive - uint16_t sectors_per_fat; - uint16_t sectors_per_track; - uint16_t num_heads; - uint32_t hidden_sectors; - uint32_t large_sectors; // Used if small_sectors == 0 - uint8_t disk_number; // Starts at 0x80 - uint8_t current_head; - uint8_t signature; // Must be 0x28 or 0x29 - uint32_t serial_number; // Unique ID created by mkfs - char volume_label[11]; // Deprecated - char system_id[8]; // FAT12 or FAT16 - } __attribute__((packed)); + /** Legacy BIOS Parameter Block */ + struct BPB { + uint16_t bytes_per_sector; + uint8_t sectors_per_cluster; + uint16_t reserved_sectors; + uint8_t fa_tables; + uint16_t root_entries; + uint16_t small_sectors; + uint8_t media_type; // 0xF8 == hard drive + uint16_t sectors_per_fat; + uint16_t sectors_per_track; + uint16_t num_heads; + uint32_t hidden_sectors; + uint32_t large_sectors; // Used if small_sectors == 0 + uint8_t disk_number; // Starts at 0x80 + uint8_t current_head; + uint8_t signature; // Must be 0x28 or 0x29 + uint32_t serial_number; // Unique ID created by mkfs + char volume_label[11]; // Deprecated + char system_id[8]; // FAT12 or FAT16 + } __attribute__((packed)); - struct mbr { - uint8_t jump[3]; - char oem_name[8]; - uint8_t boot[435]; // Boot-code - partition part[PARTITIONS]; - uint16_t magic; // 0xAA55 + struct mbr { + uint8_t jump[3]; + char oem_name[8]; + uint8_t boot[435]; // Boot-code + partition part[PARTITIONS]; + uint16_t magic; // 0xAA55 - inline BPB* bpb() noexcept - { return reinterpret_cast(boot); } - } __attribute__((packed)); + inline BPB* bpb() noexcept + { return reinterpret_cast(boot); } + } __attribute__((packed)); - static std::string id_to_name(uint8_t); -}; //< struct MBR + static std::string id_to_name(uint8_t); + }; //< struct MBR } //< namespace fs diff --git a/api/fs/memdisk.hpp b/api/fs/memdisk.hpp index d5397a74d4..f2e84244ea 100644 --- a/api/fs/memdisk.hpp +++ b/api/fs/memdisk.hpp @@ -6,9 +6,9 @@ // 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. @@ -25,36 +25,39 @@ namespace fs { -class MemDisk : public hw::IDiskDevice { -public: - static constexpr size_t SECTOR_SIZE = 512; - - MemDisk() noexcept; - - /** Returns the optimal block size for this device. */ - virtual block_t block_size() const noexcept override - { return SECTOR_SIZE; } - - virtual const char* name() const noexcept override - { - return "MemDisk"; - } - - virtual void - read(block_t blk, on_read_func reader) override; - - virtual void - read(block_t start, block_t cnt, on_read_func reader) override; - - virtual buffer_t read_sync(block_t blk) override; - - virtual block_t size() const noexcept override; - -private: - void* image_start; - void* image_end; -}; //< class MemDisk - + class MemDisk : public hw::IDiskDevice { + public: + static constexpr size_t SECTOR_SIZE = 512; + + explicit MemDisk() noexcept; + + virtual const char* name() const noexcept override + { return "MemDisk"; } + + virtual block_t size() const noexcept override; + + /** Returns the optimal block size for this device. */ + virtual block_t block_size() const noexcept override + { return SECTOR_SIZE; } + + virtual void + read(block_t blk, on_read_func reader) override { + reader( read_sync(blk) ); + } + + virtual void + read(block_t blk, size_t cnt, on_read_func reader) override { + reader( read_sync(blk, cnt) ); + } + + virtual buffer_t read_sync(block_t blk) override; + virtual buffer_t read_sync(block_t blk, size_t cnt) override; + + private: + const char* const image_start_; + const char* const image_end_; + }; //< class MemDisk + } //< namespace fs #endif //< FS_MEMDISK_HPP diff --git a/api/fs/path.hpp b/api/fs/path.hpp index 55bcf088db..2abd3e7449 100644 --- a/api/fs/path.hpp +++ b/api/fs/path.hpp @@ -24,79 +24,79 @@ namespace fs { -class Path { -public: - //! constructs Path to the current directory - Path(); + class Path { + public: + //! constructs Path to the current directory + Path(); - //! constructs Path to @path - Path(const std::string& path); + //! constructs Path to @path + Path(const std::string& path); - size_t size() const noexcept - { return stk.size(); } + size_t size() const noexcept + { return stk.size(); } - const std::string& operator [] (const int i) const noexcept - { return stk[i]; } + const std::string& operator [] (const int i) const noexcept + { return stk[i]; } - int getState() const noexcept - { return state; } + int getState() const noexcept + { return state; } - Path& operator = (const std::string& p) { - stk.clear(); - this->state = parse(p); - return *this; - } + Path& operator = (const std::string& p) { + stk.clear(); + this->state = parse(p); + return *this; + } - Path& operator += (const std::string& p) { - this->state = parse(p); - return *this; - } + Path& operator += (const std::string& p) { + this->state = parse(p); + return *this; + } - Path operator + (const std::string& p) const { - Path np = Path(*this); - np.state = np.parse(p); - return np; - } + Path operator + (const std::string& p) const { + Path np = Path(*this); + np.state = np.parse(p); + return np; + } - bool operator == (const Path& p) const { - if (stk.size() not_eq p.stk.size()) return false; - return this->to_string() == p.to_string(); - } + bool operator == (const Path& p) const { + if (stk.size() not_eq p.stk.size()) return false; + return this->to_string() == p.to_string(); + } - bool operator != (const Path& p) const - { return not this->operator == (p); } + bool operator != (const Path& p) const + { return not this->operator == (p); } - bool operator == (const std::string& p) const - { return *this == Path(p); } + bool operator == (const std::string& p) const + { return *this == Path(p); } - bool empty() const noexcept - { return stk.empty(); } + bool empty() const noexcept + { return stk.empty(); } - std::string front() const - { return stk.front(); } + std::string front() const + { return stk.front(); } - std::string back() const - { return stk.back(); } + std::string back() const + { return stk.back(); } - Path& pop_front() noexcept - { stk.pop_front(); return *this; } + Path& pop_front() noexcept + { stk.pop_front(); return *this; } - Path& pop_back() noexcept - { stk.pop_back(); return *this; } + Path& pop_back() noexcept + { stk.pop_back(); return *this; } - Path& up() - { if (not stk.empty()) stk.pop_back(); return *this; } + Path& up() + { if (not stk.empty()) stk.pop_back(); return *this; } - std::string to_string() const; + std::string to_string() const; -private: - int parse(const std::string& path); - void name_added(const std::string& name); - std::string real_path() const; + private: + int parse(const std::string& path); + void name_added(const std::string& name); + std::string real_path() const; - int state; - std::deque stk; -}; //< class Path + int state; + std::deque stk; + }; //< class Path } //< namespace fs diff --git a/api/net/socket.hpp b/api/hw/apic.hpp similarity index 64% rename from api/net/socket.hpp rename to api/hw/apic.hpp index 2fa655a44e..583786dd63 100644 --- a/api/net/socket.hpp +++ b/api/hw/apic.hpp @@ -6,9 +6,9 @@ // 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. @@ -16,25 +16,14 @@ // limitations under the License. #pragma once +#include -#include "packet.hpp" -#include -#include - -namespace net -{ - class Socket - { - // we could probably have bind() here too, - // but it needs to return the correct Socket *class* type - - int read(std::string& data); - int write(const std::string& data); - void close(); - - private: - int transmit(std::shared_ptr& pckt); - int bottom(std::shared_ptr& pckt); +namespace hw { + + class APIC { + public: + static void init(); }; + } diff --git a/api/hw/cmos.hpp b/api/hw/cmos.hpp new file mode 100644 index 0000000000..d49faa5633 --- /dev/null +++ b/api/hw/cmos.hpp @@ -0,0 +1,183 @@ +// This file is a part of the IncludeOS unikernel - www.includeos.org +// +// Copyright 2015-2016 Oslo and Akershus University College of Applied Sciences +// and Alfred Bratterud +// +// 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. + +#ifndef CMOS_HPP +#define CMOS_HPP + +#include + +/** Functions / classes for x86 CMOS / RTC interaction */ +namespace cmos { + + using port_t = const uint8_t; + using reg_t = const uint8_t; + using bit_t = const uint8_t; + + + // CMOS layout source: + // http://bos.asmhackers.net/docs/timer/docs/cmos.pdf + static port_t select = 0x70; + static port_t data = 0x71; + + static bit_t no_nmi = 0x80; + + // CMOS Time (RTC) registers + static reg_t r_sec = 0x0; + static reg_t r_min = 0x2; + static reg_t r_hrs = 0x4; + static reg_t r_dow = 0x6; + static reg_t r_day = 0x7; + static reg_t r_month = 0x8; + static reg_t r_year = 0x9; + static reg_t r_cent = 0x48; + + // RTC Alarm registers + static reg_t r_alarm_sec = 0x1; + static reg_t r_alarm_min = 0x3; + static reg_t r_alarm_hrs = 0x5; + static reg_t r_alarm_day = 0x5; + + + // Status reg. A + static reg_t r_status_a = 0xa; + static bit_t a_time_update_in_progress = 0x80; + + // Status reg. B + static reg_t r_status_b = 0xb; + static bit_t b_update_cycle_normal = 0x80; + static bit_t b_periodic_int_enabled = 0x40; + static bit_t b_alarm_int_enabled = 0x20; + static bit_t b_update_ended_int = 0x10; + static bit_t b_square_wave_enabled = 0x8; + static bit_t b_binary_mode = 0x4; + static bit_t b_24_hr_clock = 0x2; + static bit_t b_daylight_savings_enabled = 0x1; + + // Status reg. C ... (incomplete) + static reg_t r_status_c = 0xc; + static reg_t r_status_d = 0xd; + + + /** Get the contents of a CMOS register */ + inline uint8_t get(reg_t reg) { + hw::outb(select, reg | no_nmi); + return hw::inb(data); + } + + /** Set the contents of a CMOS register */ + inline void set(reg_t reg, uint8_t bits) { + printf("CMOS setting bits 0x%x in reg. 0x%x \n", bits, reg); + hw::outb(select, reg | no_nmi); + hw::outb(data, bits); + } + + /** Check if the CMOS time registers are being updated */ + inline bool update_in_progress(){ + return get(r_status_a) & a_time_update_in_progress; + } + + + /** + * A representation of x86 RTC time. + * With calculation of seconds since epoch and internet timestamp. + */ + class Time { + + public: + + /** + * Data fields of cmos::Time. + * (For initialization. Recommended practice by CG I.24) + **/ + struct Fields { + uint8_t century = 0; + uint8_t year = 0; + uint8_t month = 0; + uint8_t day_of_month = 0; + uint8_t day_of_week = 0; + uint8_t hour = 0; + uint8_t minute = 0; + uint8_t second = 0; + }; + + inline uint8_t century() { return f.century; } + inline uint16_t year() { return (f.century + 20) * 100 + f.year; } + inline uint8_t month() { return f.month; } + inline uint8_t day_of_month() { return f.day_of_month; } + inline uint8_t day_of_week() { return f.day_of_week; } + inline uint8_t hour() { return f.hour; } + inline uint8_t minute() { return f.minute; } + inline uint8_t second() { return f.second; } + + /** + * Populate with data from CMOS. + * + * @warning This is a very expensive operation causing several VM-exits + **/ + Time hw_update(); + + Time() = default; + Time(Time&) = default; + Time(Time&&) = default; + ~Time() = default; + Time& operator=(Time&) = default; + Time& operator=(Time&&) = default; + + /** Constructor with all fields. **/ + Time (Time::Fields fields) { f = fields; } + + /** Timestamp string in RFC 3339 format. Aka. Internet Timestamp. */ + std::string to_string(); + + static inline bool is_leap_year(uint32_t year) { + return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); + } + + /** Day-number of current year. **/ + int day_of_year(); + + /** + * POSIX seconds since Epoch + * http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_15 + **/ + inline uint32_t to_epoch(){ + uint32_t tm_year = year() - 1900; + uint32_t tm_yday = day_of_year() - 1; // Days since jan. 1st. + return f.second + f.minute * 60 + f.hour * 3600 + tm_yday * 86400 + + (tm_year - 70) * 31536000 + ((tm_year - 69) / 4) * 86400 - + ((tm_year - 1) / 100) * 86400 + ((tm_year + 299) / 400) * 86400; + } + + private: + + Fields f; + + }; // class cmos::Time + + /** + * Get current time. + * + * @warning: Expensive. This is a very expensive operation causing + * several VM-exits. + **/ + inline Time now() { + return Time().hw_update(); + }; + +} // namespace cmos + +#endif diff --git a/api/hw/dev.hpp b/api/hw/dev.hpp index 5571982d1d..612132016f 100644 --- a/api/hw/dev.hpp +++ b/api/hw/dev.hpp @@ -30,58 +30,58 @@ namespace hw { -/** @Todo: Implement */ -class Serial; -class APIC; -class HPET; + /** @Todo: Implement */ + class Serial; + class APIC; + class HPET; -/** - * Access point for devices - * - * Get a nic by calling `Dev::eth<0, Virtio_Net>(n)`, a disk by calling `Dev::disk<0, VirtioBlk>(n)` etc. - */ -class Dev { -public: - /** Get ethernet device n */ - template - static Nic& eth() { - static Nic eth_ {PCI_manager::device(N)}; - return eth_; - } - - /** Get disk N using driver DRIVER */ - template - static Disk& disk(Args&&... args) { - static Disk - disk_ { - PCI_manager::device(N), - std::forward(args)... - }; - return disk_; - } - - /** Get console N using driver DRIVER */ - template - static DRIVER& console() { - static DRIVER con_ {PCI_manager::device(N)}; - return con_; - } - /** - * Get serial port n - * - * @Todo: Make a serial port class, and move rsprint / rswrite etc. from OS out to it. + * Access point for devices * - * @Note: The DRIVER parameter is there to support virtio serial ports. + * Get a nic by calling `Dev::eth<0, Virtio_Net>(n)`, a disk by calling `Dev::disk<0, VirtioBlk>(n)` etc. */ - template - static PCI_Device& serial(int n); + class Dev { + public: + /** Get ethernet device n */ + template + static Nic& eth() { + static Nic eth_ {PCI_manager::device(N)}; + return eth_; + } + + /** Get disk N using driver DRIVER */ + template + static Disk& disk(Args&&... args) { + static Disk + disk_ { + PCI_manager::device(N), + std::forward(args)... + }; + return disk_; + } + + /** Get console N using driver DRIVER */ + template + static DRIVER& console() { + static DRIVER con_ {PCI_manager::device(N)}; + return con_; + } + + /** + * Get serial port n + * + * @Todo: Make a serial port class, and move rsprint / rswrite etc. from OS out to it. + * + * @Note: The DRIVER parameter is there to support virtio serial ports. + */ + template + static PCI_Device& serial(int n); - /** Programmable Interval Timer device, with ~ms-precision asynchronous timers. */ - static PIT& basic_timer() { - return PIT::instance(); - } -}; //< class Dev + /** Programmable Interval Timer device, with ~ms-precision asynchronous timers. */ + static PIT& basic_timer() { + return PIT::instance(); + } + }; //< class Dev } //< namespace hw diff --git a/api/hw/disk.hpp b/api/hw/disk.hpp index b616613fef..8583ba7869 100644 --- a/api/hw/disk.hpp +++ b/api/hw/disk.hpp @@ -23,55 +23,55 @@ namespace hw { -template -class Disk : public IDiskDevice { -public: - /** optimal block size for this device */ - virtual block_t block_size() const noexcept override - { return driver.block_size(); } + template + class Disk : public IDiskDevice { + public: + /** optimal block size for this device */ + virtual block_t block_size() const noexcept override + { return driver.block_size(); } - /** Human readable name */ - const char* name() const noexcept override - { - return driver.name(); - } + /** Human readable name */ + const char* name() const noexcept override + { + return driver.name(); + } - virtual void - read(block_t blk, on_read_func del) override - { - driver.read(blk, del); - } - virtual void - read(block_t blk, block_t count, on_read_func del) override - { - driver.read(blk, count, del); - } + virtual void + read(block_t blk, on_read_func del) override { + driver.read(blk, del); + } + virtual void + read(block_t blk, size_t count, on_read_func del) override { + driver.read(blk, count, del); + } - virtual buffer_t read_sync(block_t blk) override - { - return driver.read_sync(blk); - } + virtual buffer_t read_sync(block_t blk) override { + return driver.read_sync(blk); + } + virtual buffer_t read_sync(block_t blk, size_t cnt) override { + return driver.read_sync(blk, cnt); + } - virtual block_t size() const noexcept override - { - return driver.size(); - } + virtual block_t size() const noexcept override + { + return driver.size(); + } - virtual ~Disk() = default; + virtual ~Disk() = default; -private: - DRIVER driver; + private: + DRIVER driver; - /** - * Just a wrapper around the driver constructor - * @note The Dev-class is a friend and will call this - */ - template - explicit Disk(PCI_Device& d, Args&&... args): - driver{d, std::forward(args)... } {} + /** + * Just a wrapper around the driver constructor + * @note The Dev-class is a friend and will call this + */ + template + explicit Disk(PCI_Device& d, Args&&... args): + driver{d, std::forward(args)... } {} - friend class Dev; -}; //< class Disk + friend class Dev; + }; //< class Disk } //< namespace hw diff --git a/api/hw/disk_device.hpp b/api/hw/disk_device.hpp index 0612356c3c..a4a7472a07 100644 --- a/api/hw/disk_device.hpp +++ b/api/hw/disk_device.hpp @@ -6,9 +6,9 @@ // 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. @@ -21,44 +21,45 @@ #include #include -#include +#include namespace hw { -class IDiskDevice { -public: - using block_t = uint64_t; //< Disk device block size - using buffer_t = std::shared_ptr; - - // Delegate for result of reading a disk sector - using on_read_func = std::function; - - /** Human-readable name of this disk controller */ - virtual const char* name() const noexcept = 0; - - /** The size of the disk in whole sectors */ - virtual block_t size() const noexcept = 0; - - /** Returns the optimal block size for this device */ - virtual block_t block_size() const noexcept = 0; - - /** - * Read block(s) from blk and call func with result - * A null-pointer is passed to result if something bad happened - * Validate using !buffer_t: - * if (!buffer) - * error("Device failed to read sector"); - **/ - virtual void read(block_t blk, on_read_func func) = 0; - virtual void read(block_t blk, block_t count, on_read_func) = 0; - - /** read synchronously the block @blk */ - virtual buffer_t read_sync(block_t blk) = 0; - - /** Default destructor */ - virtual ~IDiskDevice() noexcept = default; -}; //< class IDiskDevice + class IDiskDevice { + public: + using block_t = uint64_t; //< Disk device block size + using buffer_t = std::shared_ptr; + + // Delegate for result of reading a disk sector + using on_read_func = delegate; + + /** Human-readable name of this disk controller */ + virtual const char* name() const noexcept = 0; + + /** The size of the disk in whole sectors */ + virtual block_t size() const noexcept = 0; + + /** Returns the optimal block size for this device */ + virtual block_t block_size() const noexcept = 0; + + /** + * Read block(s) from blk and call func with result + * A null-pointer is passed to result if something bad happened + * Validate using !buffer_t: + * if (!buffer) + * error("Device failed to read sector"); + **/ + virtual void read(block_t blk, on_read_func func) = 0; + virtual void read(block_t blk, size_t count, on_read_func) = 0; + + /** read synchronously the block @blk */ + virtual buffer_t read_sync(block_t blk) = 0; + virtual buffer_t read_sync(block_t blk, size_t count) = 0; + + /** Default destructor */ + virtual ~IDiskDevice() noexcept = default; + }; //< class IDiskDevice } //< namespace hw diff --git a/api/hw/ide.hpp b/api/hw/ide.hpp index 0d6ac60c4f..03610f88c1 100644 --- a/api/hw/ide.hpp +++ b/api/hw/ide.hpp @@ -19,64 +19,67 @@ #define HW_IDE_HPP #include +#include #include "disk.hpp" #include "pci_device.hpp" namespace hw { -/** IDE device driver */ -class IDE : public IDiskDevice { -public: - enum selector_t - { - MASTER, - SLAVE - }; + /** IDE device driver */ + class IDE : public IDiskDevice { + public: + enum selector_t + { + MASTER = 0x00, + SLAVE = 0x10 + }; - /** - * Constructor - * - * @param pcidev: An initialized PCI device - */ - explicit IDE(hw::PCI_Device& pcidev, selector_t); + /** + * Constructor + * + * @param pcidev: An initialized PCI device + */ + explicit IDE(hw::PCI_Device& pcidev, selector_t); - /** Human-readable name of this disk controller */ - virtual const char* name() const noexcept override - { return "IDE Controller"; } + /** Human-readable name of this disk controller */ + virtual const char* name() const noexcept override + { return "IDE Controller"; } - /** Returns the optimal block size for this device. */ - virtual block_t block_size() const noexcept override - { return 512; } + /** Returns the optimal block size for this device. */ + virtual block_t block_size() const noexcept override + { return 512; } - virtual void read(block_t blk, on_read_func reader) override; - virtual void read(block_t blk, block_t count, on_read_func reader) override; + virtual void read(block_t blk, on_read_func reader) override; + virtual void read(block_t blk, size_t cnt, on_read_func cb) override; - /** read synchronously from IDE disk */ - virtual buffer_t read_sync(block_t blk) override; + /** read synchronously from IDE disk */ + virtual buffer_t read_sync(block_t blk) override; + virtual buffer_t read_sync(block_t blk, size_t cnt) override; - virtual block_t size() const noexcept override - { return _nb_blk; } + virtual block_t size() const noexcept override + { return _nb_blk; } -private: - void set_drive(const uint8_t drive) const noexcept; - void set_nbsectors(const uint8_t cnt) const noexcept; - void set_blocknum(block_t blk) const noexcept; - void set_command(const uint16_t command) const noexcept; - void set_irq_mode(const bool on) const noexcept; + static void set_irq_mode(const bool on) noexcept; - void wait_status_busy() const noexcept; - void wait_status_flags(const int flags, const bool set) const noexcept; + static void wait_status_busy() noexcept; + static void wait_status_flags(const int flags, const bool set) noexcept; - void irq_handler(); - void enable_irq_handler(); + private: + void set_drive(const uint8_t drive) const noexcept; + void set_nbsectors(const uint8_t cnt) const noexcept; + void set_blocknum(block_t blk) const noexcept; + void set_command(const uint16_t command) const noexcept; -private: - hw::PCI_Device& _pcidev; // PCI device - uint8_t _drive; // Drive id (IDE_MASTER or IDE_SLAVE) - uint32_t _iobase; // PCI device io base address - block_t _nb_blk; // Max nb blocks of the device -}; //< class IDE + void callback_wrapper(); + void enable_irq_handler(); + + private: + hw::PCI_Device& _pcidev; // PCI device + uint8_t _drive; // Drive id (IDE_MASTER or IDE_SLAVE) + uint32_t _iobase; // PCI device io base address + block_t _nb_blk; // Max nb blocks of the device + }; //< class IDE } //< namespace hw diff --git a/api/hw/nic.hpp b/api/hw/nic.hpp index e2788d91fd..4e670ac535 100644 --- a/api/hw/nic.hpp +++ b/api/hw/nic.hpp @@ -26,68 +26,88 @@ namespace hw { -/** - * A public interface for Network cards - * - * @note: The requirements for a driver is implicitly given by how it's used below, - * rather than explicitly by inheritance. This avoids vtables. - * - * @note: Drivers are passed in as template paramter so that only the drivers - * we actually need will be added to our project. - */ -template -class Nic { -public: - using driver_t = DRIVER; - - /** Get a readable name. */ - inline const char* name() const noexcept - { return driver_.name(); } - - /** The mac address. */ - inline const net::Ethernet::addr& mac() - { return driver_.mac(); } - - inline void set_linklayer_out(net::upstream del) - { driver_.set_linklayer_out(del); } - - inline net::upstream get_linklayer_out() - { return driver_.get_linklayer_out(); } - - inline void transmit(net::Packet_ptr pckt) - { driver_.transmit(pckt); } - - inline uint16_t MTU() const noexcept - { return driver_.MTU(); } - - inline net::BufferStore& bufstore() noexcept - { return driver_.bufstore(); } - -private: - driver_t driver_; - /** - * Constructor + * A public interface for Network cards * - * Just a wrapper around the driver constructor. + * @note: The requirements for a driver is implicitly given by how it's used below, + * rather than explicitly by inheritance. This avoids vtables. * - * @note: The Dev-class is a friend and will call this + * @note: Drivers are passed in as template paramter so that only the drivers + * we actually need will be added to our project. */ - Nic(PCI_Device& d) : driver_{d} {} + template + class Nic { + public: + using driver_t = DRIVER; - friend class Dev; -}; + /** Get a readable name. */ + inline const char* name() const noexcept + { return driver_.name(); } -/** Future drivers may start out like so, */ -class E1000 { -public: - inline const char* name() const noexcept - { return "E1000 Driver"; } - //...whatever the Nic class implicitly needs -}; -/** Hopefully somebody will port a driver for this one */ -class RTL8139; + /** The mac address. */ + inline const net::Ethernet::addr& mac() + { return driver_.mac(); } + + inline void set_linklayer_out(net::upstream del) + { driver_.set_linklayer_out(del); } + + inline net::upstream get_linklayer_out() + { return driver_.get_linklayer_out(); } + + inline void transmit(net::Packet_ptr pckt) + { driver_.transmit(pckt); } + + inline uint16_t MTU() const noexcept + { return driver_.MTU(); } + + inline uint16_t bufsize() const noexcept + { return driver_.bufsize(); } + + inline net::BufferStore& bufstore() noexcept + { return driver_.bufstore(); } + + inline void on_transmit_queue_available(net::transmit_avail_delg del) + { driver_.on_transmit_queue_available(del); } + + inline size_t transmit_queue_available() + { return driver_.transmit_queue_available(); } + + inline size_t receive_queue_waiting(){ + return driver_.receive_queue_waiting(); + }; + + inline size_t buffers_available() + { return bufstore().buffers_available(); } + + inline void on_exit_to_physical(delegate dlg) + { driver_.on_exit_to_physical(dlg); } + + private: + driver_t driver_; + + /** + * Constructor + * + * Just a wrapper around the driver constructor. + * + * @note: The Dev-class is a friend and will call this + */ + Nic(PCI_Device& d) : driver_{d} {} + + friend class Dev; + }; + + /** Future drivers may start out like so, */ + class E1000 { + public: + inline const char* name() const noexcept + { return "E1000 Driver"; } + //...whatever the Nic class implicitly needs + }; + + /** Hopefully somebody will port a driver for this one */ + class RTL8139; } //< namespace hw diff --git a/api/hw/pci.hpp b/api/hw/pci.hpp index f839eae85d..6bef855e3d 100644 --- a/api/hw/pci.hpp +++ b/api/hw/pci.hpp @@ -5,53 +5,53 @@ namespace hw { -typedef uint16_t port_t; + typedef uint16_t port_t; -static inline int inp(port_t port) -{ - int ret; + static inline int inp(port_t port) + { + int ret; - __asm__ volatile("xorl %eax,%eax"); - __asm__ volatile("inb %%dx,%%al" - :"=a"(ret) - :"d"(port)); - return ret; -} - -static inline uint16_t inpw(port_t port) -{ - uint16_t ret; - __asm__ volatile("xorl %eax,%eax"); - __asm__ volatile("inw %%dx,%%ax" - :"=a"(ret) - :"d"(port)); - return ret; -} - -static inline uint32_t inpd(port_t port) -{ - uint32_t ret; - __asm__ volatile("xorl %eax,%eax"); - __asm__ volatile("inl %%dx,%%eax" - :"=a"(ret) - :"d"(port)); + __asm__ volatile("xorl %eax,%eax"); + __asm__ volatile("inb %%dx,%%al" + :"=a"(ret) + :"d"(port)); + return ret; + } + + static inline uint16_t inpw(port_t port) + { + uint16_t ret; + __asm__ volatile("xorl %eax,%eax"); + __asm__ volatile("inw %%dx,%%ax" + :"=a"(ret) + :"d"(port)); + return ret; + } + + static inline uint32_t inpd(port_t port) + { + uint32_t ret; + __asm__ volatile("xorl %eax,%eax"); + __asm__ volatile("inl %%dx,%%eax" + :"=a"(ret) + :"d"(port)); - return ret; -} - - -static inline void outp(port_t port, uint8_t data) -{ - __asm__ volatile ("outb %%al,%%dx"::"a" (data), "d"(port)); -} -static inline void outpw(port_t port, uint16_t data) -{ - __asm__ volatile ("outw %%ax,%%dx"::"a" (data), "d"(port)); -} -static inline void outpd(port_t port, uint32_t data) -{ - __asm__ volatile ("outl %%eax,%%dx"::"a" (data), "d"(port)); -} + return ret; + } + + + static inline void outp(port_t port, uint8_t data) + { + __asm__ volatile ("outb %%al,%%dx"::"a" (data), "d"(port)); + } + static inline void outpw(port_t port, uint16_t data) + { + __asm__ volatile ("outw %%ax,%%dx"::"a" (data), "d"(port)); + } + static inline void outpd(port_t port, uint32_t data) + { + __asm__ volatile ("outl %%eax,%%dx"::"a" (data), "d"(port)); + } } //< namespace hw diff --git a/api/hw/pci_device.hpp b/api/hw/pci_device.hpp index 4e200fa1a3..065e459408 100644 --- a/api/hw/pci_device.hpp +++ b/api/hw/pci_device.hpp @@ -23,244 +23,244 @@ namespace PCI { -static const uint16_t CONFIG_ADDR {0xCF8U}; -static const uint16_t CONFIG_DATA {0xCFCU}; -static const uint8_t CONFIG_INTR {0x3CU}; + static const uint16_t CONFIG_ADDR {0xCF8U}; + static const uint16_t CONFIG_DATA {0xCFCU}; + static const uint8_t CONFIG_INTR {0x3CU}; -static const uint8_t CONFIG_VENDOR {0x00U}; -static const uint8_t CONFIG_CLASS_REV {0x08U}; + static const uint8_t CONFIG_VENDOR {0x00U}; + static const uint8_t CONFIG_CLASS_REV {0x08U}; -static const uint8_t CONFIG_BASE_ADDR_0 {0x10U}; + static const uint8_t CONFIG_BASE_ADDR_0 {0x10U}; -static const uint32_t BASE_ADDRESS_MEM_MASK {~0x0FUL}; -static const uint32_t BASE_ADDRESS_IO_MASK {~0x03UL}; + static const uint32_t BASE_ADDRESS_MEM_MASK {~0x0FUL}; + static const uint32_t BASE_ADDRESS_IO_MASK {~0x03UL}; -static const uint32_t WTF {0xffffffffU}; + static const uint32_t WTF {0xffffffffU}; -/** - * @brief PCI device message format - * - * Used to communicate with PCI devices - */ -union msg { - - //! The whole message - uint32_t data; - - /** - * Packed attribtues, ordered low to high. - * - * @note: Doxygen thinks this is a function - it's not + /** + * @brief PCI device message format * - * it's a GCC-directive. + * Used to communicate with PCI devices */ - struct __attribute__((packed)) { - //! The PCI register - uint8_t reg; + union msg { + + //! The whole message + uint32_t data; + + /** + * Packed attribtues, ordered low to high. + * + * @note: Doxygen thinks this is a function - it's not + * + * it's a GCC-directive. + */ + struct __attribute__((packed)) { + //! The PCI register + uint8_t reg; - //! The 16-bit PCI-address @see pci_addr() - uint16_t addr; - uint8_t code; - }; -}; //< union msg + //! The 16-bit PCI-address @see pci_addr() + uint16_t addr; + uint8_t code; + }; + }; //< union msg -/** Relevant class codes (many more) */ -enum classcode_t { - OLD, - STORAGE, - NIC, - DISPLAY, - MULTIMEDIA, - MEMORY, - BRIDGE, - COMMUNICATION, - BASE_SYSTEM_PER, - INPUT_DEVICE, - DOCKING_STATION, - PROCESSOR, - SERIAL_BUS, - WIRELESS, - IO_CTL, - SATELLITE, - ENCRYPTION, - SIGPRO, - OTHER=255 -}; //< enum classcode_t + /** Relevant class codes (many more) */ + enum classcode_t { + OLD, + STORAGE, + NIC, + DISPLAY, + MULTIMEDIA, + MEMORY, + BRIDGE, + COMMUNICATION, + BASE_SYSTEM_PER, + INPUT_DEVICE, + DOCKING_STATION, + PROCESSOR, + SERIAL_BUS, + WIRELESS, + IO_CTL, + SATELLITE, + ENCRYPTION, + SIGPRO, + OTHER=255 + }; //< enum classcode_t } //< namespace PCI namespace hw { -/** - * @brief Communication class for all PCI devices - * - * All low level communication with PCI devices should (ideally) go here. - * - * @todo - * - Consider if we ever need to separate the address into 'bus/dev/func' parts. - * - Do we ever need anything but PCI Devices? -*/ -class PCI_Device { // public Device //Why not? A PCI device is too general to be accessible? -public: - - enum { - VENDOR_AMD = 0x1022, - VENDOR_INTEL = 0x8086, - VENDOR_CIRRUS = 0x1013, - VENDOR_VIRTIO = 0x1AF4, - VENDOR_REALTEK = 0x10EC - }; - /** - * Constructor - * - * @param pci_addr: A 16-bit PCI address. - * @param device_id: A device ID, consisting of PCI vendor and product ID's. - * - * @see pci_addr() for more about the address + * @brief Communication class for all PCI devices + * + * All low level communication with PCI devices should (ideally) go here. + * + * @todo + * - Consider if we ever need to separate the address into 'bus/dev/func' parts. + * - Do we ever need anything but PCI Devices? */ - explicit PCI_Device(const uint16_t pci_addr, const uint32_t device_id) noexcept; + class PCI_Device { // public Device //Why not? A PCI device is too general to be accessible? + public: - //! @brief Read from device with implicit pci_address (e.g. used by Nic) - uint32_t read_dword(const uint8_t reg) noexcept; + enum { + VENDOR_AMD = 0x1022, + VENDOR_INTEL = 0x8086, + VENDOR_CIRRUS = 0x1013, + VENDOR_VIRTIO = 0x1AF4, + VENDOR_REALTEK = 0x10EC + }; - //! @brief Read from device with explicit pci_addr - static uint32_t read_dword(const uint16_t pci_addr, const uint8_t reg) noexcept; + /** + * Constructor + * + * @param pci_addr: A 16-bit PCI address. + * @param device_id: A device ID, consisting of PCI vendor and product ID's. + * + * @see pci_addr() for more about the address + */ + explicit PCI_Device(const uint16_t pci_addr, const uint32_t device_id) noexcept; + + //! @brief Read from device with implicit pci_address (e.g. used by Nic) + uint32_t read_dword(const uint8_t reg) noexcept; - /** - * Probe for a device on the given address - * - * @param pci_addr: the address to probe - * - * @deprecated We got a 20% performance degradation using this for probing - * - * @see PCI_Device() - */ - static PCI_Device* Create(uint16_t pci_addr); + //! @brief Read from device with explicit pci_addr + static uint32_t read_dword(const uint16_t pci_addr, const uint8_t reg) noexcept; - // @brief Get a device by address. @see pci_addr(). - static PCI_Device* get(uint16_t pci_addr); + /** + * Probe for a device on the given address + * + * @param pci_addr: the address to probe + * + * @deprecated We got a 20% performance degradation using this for probing + * + * @see PCI_Device() + */ + static PCI_Device* Create(uint16_t pci_addr); - // @brief Get a device by individual address parts. - // @todo Will we ever need this? - static PCI_Device* get(int busno, int devno, int funcno); + // @brief Get a device by address. @see pci_addr(). + static PCI_Device* get(uint16_t pci_addr); + + // @brief Get a device by individual address parts. + // @todo Will we ever need this? + static PCI_Device* get(int busno, int devno, int funcno); - /** A descriptive name */ - inline const char* name(); + /** A descriptive name */ + inline const char* name(); - /** - * Get the PCI address of device. - * - * The address is a composite of 'bus', 'device' and 'function', usually used - * (i.e. by Linux) to designate a PCI device. - * - * @return: The address of the device - */ - inline uint16_t pci_addr() const noexcept - { return pci_addr_; }; + /** + * Get the PCI address of device. + * + * The address is a composite of 'bus', 'device' and 'function', usually used + * (i.e. by Linux) to designate a PCI device. + * + * @return: The address of the device + */ + inline uint16_t pci_addr() const noexcept + { return pci_addr_; }; - /** Get the pci class code. */ - inline PCI::classcode_t classcode() const noexcept - { return static_cast(devtype_.classcode); } + /** Get the pci class code. */ + inline PCI::classcode_t classcode() const noexcept + { return static_cast(devtype_.classcode); } - inline uint16_t rev_id() const noexcept - { return devtype_.rev_id; } + inline uint16_t rev_id() const noexcept + { return devtype_.rev_id; } - /** Get the pci vendor and product id */ - inline uint16_t vendor_id() const noexcept - { return device_id_.vendor; } + /** Get the pci vendor and product id */ + inline uint16_t vendor_id() const noexcept + { return device_id_.vendor; } - inline uint16_t product_id() const noexcept - { return device_id_.product; } + inline uint16_t product_id() const noexcept + { return device_id_.product; } - /** - * Parse all Base Address Registers (BAR's) - * - * Used to determine how to communicate with the device. - * - * This function adds resources to the PCI_Device. - */ - void probe_resources() noexcept; + /** + * Parse all Base Address Registers (BAR's) + * + * Used to determine how to communicate with the device. + * + * This function adds resources to the PCI_Device. + */ + void probe_resources() noexcept; - /** The base address of the (first) I/O resource */ - uint32_t iobase() const noexcept; + /** The base address of the (first) I/O resource */ + uint32_t iobase() const noexcept; -private: - // @brief The 3-part PCI address - uint16_t pci_addr_; + private: + // @brief The 3-part PCI address + uint16_t pci_addr_; - //@brief The three address parts derived (if needed) - uint8_t busno_ {0}; - uint8_t devno_ {0}; - uint8_t funcno_ {0}; + //@brief The three address parts derived (if needed) + uint8_t busno_ {0}; + uint8_t devno_ {0}; + uint8_t funcno_ {0}; - // @brief The 2-part ID retrieved from the device - union vendor_product { - uint32_t __value; - struct __attribute__((packed)) { - uint16_t vendor; - uint16_t product; - }; - } device_id_; + // @brief The 2-part ID retrieved from the device + union vendor_product { + uint32_t __value; + struct __attribute__((packed)) { + uint16_t vendor; + uint16_t product; + }; + } device_id_; - // @brief The class code (device type) - union class_revision { - uint32_t reg; - struct __attribute__((packed)) { - uint8_t rev_id; - uint8_t prog_if; - uint8_t subclass; - uint8_t classcode; - }; - struct __attribute__((packed)) { - uint16_t class_subclass; - uint8_t __prog_if; //Overlaps the above - uint8_t revision; - }; - } devtype_; + // @brief The class code (device type) + union class_revision { + uint32_t reg; + struct __attribute__((packed)) { + uint8_t rev_id; + uint8_t prog_if; + uint8_t subclass; + uint8_t classcode; + }; + struct __attribute__((packed)) { + uint16_t class_subclass; + uint8_t __prog_if; //Overlaps the above + uint8_t revision; + }; + } devtype_; - // @brief Printable names - const char* classname_; - const char* vendorname_; - const char* productname_; + // @brief Printable names + const char* classname_; + const char* vendorname_; + const char* productname_; - // Device Resources + // Device Resources - //! @brief Resource types, "Memory" or "I/O" - enum resource_t { RES_MEM, RES_IO }; + //! @brief Resource types, "Memory" or "I/O" + enum resource_t { RES_MEM, RES_IO }; - /** A device resource - possibly a list */ - template - struct Resource { - const resource_t type {RT}; - uint32_t start_; - uint32_t len_; - Resource* next {nullptr}; - Resource(const uint32_t start, const uint32_t len) : start_{start}, len_{len} {}; - }; + /** A device resource - possibly a list */ + template + struct Resource { + const resource_t type {RT}; + uint32_t start_; + uint32_t len_; + Resource* next {nullptr}; + Resource(const uint32_t start, const uint32_t len) : start_{start}, len_{len} {}; + }; - //! @brief Resource lists. Members added by add_resource(); - Resource* res_mem_ {nullptr}; - Resource* res_io_ {nullptr}; + //! @brief Resource lists. Members added by add_resource(); + Resource* res_mem_ {nullptr}; + Resource* res_io_ {nullptr}; - //! @brief Write to device with implicit pci_address (e.g. used by Nic) - void write_dword(const uint8_t reg, const uint32_t value) noexcept; + //! @brief Write to device with implicit pci_address (e.g. used by Nic) + void write_dword(const uint8_t reg, const uint32_t value) noexcept; - /** - * Add a resource to a resource queue. - * - * (This seems pretty dirty; private class, reference to pointer etc.) */ - template - void add_resource(Resource* res, Resource*& Q) noexcept { - Resource* q; - if (Q) { - q = Q; - while (q->next) q = q->next; - q->next = res; - } else { - Q = res; + /** + * Add a resource to a resource queue. + * + * (This seems pretty dirty; private class, reference to pointer etc.) */ + template + void add_resource(Resource* res, Resource*& Q) noexcept { + Resource* q; + if (Q) { + q = Q; + while (q->next) q = q->next; + q->next = res; + } else { + Q = res; + } } - } -}; //< class PCI_Device + }; //< class PCI_Device } //< namespace hw diff --git a/api/hw/pic.hpp b/api/hw/pic.hpp index ef292ffe40..c08b346f77 100644 --- a/api/hw/pic.hpp +++ b/api/hw/pic.hpp @@ -6,9 +6,9 @@ // 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. @@ -20,87 +20,138 @@ #include "../kernel/os.hpp" #include "ioport.hpp" +#include namespace hw { -class PIC { -public: - static void init() noexcept; - - inline static void enable_irq(uint8_t irq) noexcept { - irq_mask_ &= ~(1 << irq); - if (irq >= 8) irq_mask_ &= ~(1 << 2); - set_intr_mask(irq_mask_); - INFO2("+ Enabling IRQ %i, mask: 0x%x", irq, irq_mask_); - } - - inline static void disable_irq(const uint8_t irq) noexcept { - irq_mask_ |= (1 << irq); - if ((irq_mask_ & 0xFF00) == 0xFF00) { - irq_mask_ |= (1 << 2); + /** + Programmable Interrupt Controller + implementation according to Intel 8259A / 8259A-2 1988 whitepaper + */ + class PIC { + public: + static void init() noexcept; + + inline static void enable_irq(uint8_t irq) noexcept { + irq_mask_ &= ~(1 << irq); + if (irq >= 8) irq_mask_ &= ~(1 << 2); + set_intr_mask(irq_mask_); + INFO2("+ Enabling IRQ %i, mask: 0x%x", irq, irq_mask_); + } + + inline static void disable_irq(const uint8_t irq) noexcept { + irq_mask_ |= (1 << irq); + if ((irq_mask_ & 0xFF00) == 0xFF00) { + irq_mask_ |= (1 << 2); + } + set_intr_mask(irq_mask_); + INFO2("- Disabling IRQ %i, mask: 0x%x", irq, irq_mask_); + } + + /** + @brief End of Interrupt. Master IRQ-lines (0-7) currently use Auto EOI, + but the user should assume that IRQ-specific EOI's are necessary. + + @note: + According to Intel 8259A / 8259A-2 whitepaper p. 15 + "The AEOI mode can only be used in a master 8259A and not a slave. + 8259As with a copyright date of 1985 or later will operate in the AEOI + mode as a master or a slave" + + If I enable auto-eoi for slave, everything seems to freeze in Qemu, + the moment I get the first network interrupt (IRQ 11). + + I'm assuming this means that I have an old chip :-) + */ + inline static void eoi(const uint8_t irq) noexcept { + + // We're using auto-EOI-mode for IRQ < 8, IRQ >= 16 are soft-irq's + if (irq >= 8 && irq < 16) { + hw::outb(slave_ctrl, ocw2_specific_eoi | (irq - 8)); + } + + // If we switch off auto-EOI, we need to re-enable this + // I'm disabling it in order to save VM-exits + //hw::outb(master_ctrl, specific_eoi_ | irq );*/ + } + + /* Returns the combined value of the cascaded PICs irq request register */ + inline static uint16_t get_irr() noexcept + { return get_irq_reg(ocw3_read_irr); } + + /* Returns the combined value of the cascaded PICs in-service register */ + inline static uint16_t get_isr() noexcept + { return get_irq_reg(ocw3_read_isr); } + + private: + // ICW1 bits + static constexpr uint8_t icw1 {0x10}; // Bit 5 compulsory + static constexpr uint8_t icw1_icw4_needed {0x1}; // Prepare for cw4 or not + static constexpr uint8_t icw1_single_mode {0x2}; // 0: cascade + static constexpr uint8_t icw1_addr_interval_4 {0x4}; // 0: interval 8 + static constexpr uint8_t icw1_level_trigered {0x8}; // 0: egde triggred + + // ICW2 bits: Interrupt number for first IRQ + static constexpr uint8_t icw2_irq_base_master {32}; + static constexpr uint8_t icw2_irq_base_slave {40}; + + // ICW3 bits: Location and ID of slave PIC + static constexpr uint8_t icw3_slave_location {0x04}; + static constexpr uint8_t icw3_slave_id {0x02}; + + // ICW4 bits: + static constexpr uint8_t icw4 {0x0}; // No bits by default + static constexpr uint8_t icw4_8086_mode {0x1}; // 0: aincient runes + static constexpr uint8_t icw4_auto_eoi {0x2}; // auto vs. normal EOI + static constexpr uint8_t icw4_buffered_mode_slave {0x08}; + static constexpr uint8_t icw4_buffered_mode_master {0x12}; + + + // Registers addresses + static const uint8_t master_ctrl {0x20}; + static const uint8_t master_mask {0x21}; + static const uint8_t slave_ctrl {0xA0}; + static const uint8_t slave_mask {0xA1}; + + + // Operational command words + // OCW1: Interrupt mask + // OCW2: + static constexpr uint8_t ocw2 {0x0}; // No default bits + static constexpr uint8_t ocw2_nonspecific_eoi {0x20}; + static constexpr uint8_t ocw2_specific_eoi {0x60}; + static constexpr uint8_t ocw2_rotate_on_non_specific_eoi {0xA0}; + static constexpr uint8_t ocw2_rotate_on_auto_eoi_set {0x80}; // 0x0 to clear + static constexpr uint8_t ocw2_rotate_on_specific_eoi {0xE0}; + static constexpr uint8_t ocw2_set_priority_cmd {0xC0}; + static constexpr uint8_t ocw2_nop {0x40}; + + // OCW3: + static constexpr uint8_t ocw3 {0x08}; // Default bits + static constexpr uint8_t ocw3_read_irr {0x02}; + static constexpr uint8_t ocw3_read_isr {0x03}; + static constexpr uint8_t ocw3_poll_cmd {0x04}; // 0 to disable + static constexpr uint8_t ocw3_set_special_mask {0x60}; + static constexpr uint8_t ocw3_reset_special_mask {0x40}; + + static uint16_t irq_mask_; + + inline static void set_intr_mask(uint32_t mask) noexcept { + hw::outb(master_mask, static_cast(mask)); + hw::outb(slave_mask, static_cast(mask >> 8)); } - set_intr_mask(irq_mask_); - INFO2("- Disabling IRQ %i, mask: 0x%x", irq, irq_mask_); - } - inline static void eoi(const uint8_t irq) noexcept { - if (irq >= 8) { - hw::outb(slave_ctrl, eoi_); + /* Helper func */ + inline static uint16_t get_irq_reg(const int ocw3) noexcept { + /* + * OCW3 to PIC CMD to get the register values. PIC2 is chained, and + * represents IRQs 8-15. PIC1 is IRQs 0-7, with 2 being the chain + */ + hw::outb(master_ctrl, ocw3); + hw::outb(master_ctrl, ocw3); + return (hw::inb(slave_ctrl) << 8) | hw::inb(master_ctrl); } - hw::outb(master_ctrl, eoi_); - } - - /* Returns the combined value of the cascaded PICs irq request register */ - inline static uint16_t get_irr() noexcept - { return get_irq_reg(read_irr); } - - /* Returns the combined value of the cascaded PICs in-service register */ - inline static uint16_t get_isr() noexcept - { return get_irq_reg(read_isr); } - -private: - static const uint8_t master_ctrl {0x20}; - static const uint8_t master_mask {0x21}; - static const uint8_t slave_ctrl {0xA0}; - static const uint8_t slave_mask {0xA1}; - - // Master commands - static const uint8_t master_icw1 {0x11}; - static const uint8_t master_icw2 {0x20}; - static const uint8_t master_icw3 {0x04}; - static const uint8_t master_icw4 {0x01}; - - // Slave commands - static const uint8_t slave_icw1 {0x11}; - static const uint8_t slave_icw2 {0x28}; - static const uint8_t slave_icw3 {0x02}; - static const uint8_t slave_icw4 {0x01}; - - /* IRQ ready next CMD read */ - static const uint8_t read_irr {0x0A}; - /* IRQ service next CMD read */ - static const uint8_t read_isr {0x0B}; - - static const uint8_t eoi_ {0x20}; - - static uint16_t irq_mask_; - - inline static void set_intr_mask(uint32_t mask) noexcept { - hw::outb(master_mask, static_cast(mask)); - hw::outb(slave_mask, static_cast(mask >> 8)); - } - - /* Helper func */ - inline static uint16_t get_irq_reg(const int ocw3) noexcept { - /* - * OCW3 to PIC CMD to get the register values. PIC2 is chained, and - * represents IRQs 8-15. PIC1 is IRQs 0-7, with 2 being the chain - */ - hw::outb(master_ctrl, ocw3); - hw::outb(master_ctrl, ocw3); - return (hw::inb(slave_ctrl) << 8) | hw::inb(master_ctrl); - } -}; //< class PIC + }; //< class PIC } //< namespace hw diff --git a/api/hw/pit.hpp b/api/hw/pit.hpp index 7e29aff3a6..d3bf92a157 100644 --- a/api/hw/pit.hpp +++ b/api/hw/pit.hpp @@ -6,9 +6,9 @@ // 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. @@ -23,165 +23,183 @@ namespace hw { -/** - Programmable Interval Timer class. A singleton. - - @TODO - ...It has timer-functionality, which should probably be super-classed, - so that i.e. the HPET could be used with the same interface. -*/ -class PIT{ -public: - - typedef delegate timeout_handler; - typedef std::function repeat_condition; - - /** Create a one-shot timer. - @param ms: Expiration time. Compatible with all std::chrono durations. - @param handler: A delegate or function to be called on timeout. */ - void onTimeout(std::chrono::milliseconds ms, timeout_handler handler); - - /** Create a repeating timer. - @param ms: Expiration time. Compatible with all std::chrono durations. - @param handler: A delegate or function to be called every ms interval. - @param cond: The timer ends when cond() returns false. Default to true. */ - void onRepeatedTimeout(std::chrono::milliseconds ms, - timeout_handler handler, - repeat_condition cond = forever); - - /** No copy or move. The OS owns one instance forever. */ - PIT(PIT&) = delete; - PIT(PIT&&) = delete; - - /** Get the (single) instance. */ - static PIT& instance() { - static PIT instance_; - return instance_; - }; - - /** Initialize the hardware. */ - static void init(); - - /** The constant frequency of the PIT-hardware, before frequency dividers */ - static constexpr MHz frequency() { return frequency_; } - - static inline MHz current_frequency(){ return frequency() / current_freq_divider_; } - - /** Estimate cpu frequency based on the fixed PIT frequency and rdtsc. - @Note This is an asynchronous function. Once finished the result can be - fetched by CPUFrequency() (below) */ - static void estimateCPUFrequency(); - - /** Get the last estimated CPU frequency. May trigger frequency sampling */ - static MHz CPUFrequency(); - -private: - // Default repeat-condition - static std::function forever; - - enum Mode { ONE_SHOT = 0, - HW_ONESHOT = 1 << 1, - RATE_GEN = 2 << 1, - SQ_WAVE = 3 << 1, - SW_STROBE = 4 << 1, - HW_STROBE = 5 << 1, - NONE = 256}; - - // The PIT-chip runs at this fixed frequency (in MHz) , according to OSDev.org */ - static constexpr MHz frequency_ = MHz(14.31818 / 12); - - /** Disable regular timer interrupts- which are turned on at boot-time. */ - static void disable_regular_interrupts(); - - /** The default (soft)handler for timer interrupts */ - void irq_handler(); - - // Private constructor / destructor. It's a singleton. - PIT(); - ~PIT(); - - - // State-keeping - static Mode temp_mode_; - static uint16_t temp_freq_divider_; - static uint8_t status_byte_; - static uint16_t current_freq_divider_; - static Mode current_mode_; - static uint64_t IRQ_counter_; - - - // The closest we can get to a millisecond interval, with the PIT-frequency - static constexpr uint16_t millisec_interval = KHz(frequency_).count(); - - // Count the "milliseconds" - static uint64_t millisec_counter; - - // Access mode bits are bits 4- and 5 in the Mode register - enum AccessMode { LATCH_COUNT = 0x0, LO_ONLY=0x10, HI_ONLY=0x20, LO_HI=0x30 }; - - /** Physically set the PIT-mode */ - static void set_mode(Mode); - - /** Physiclally set the PIT frequency divider */ - static void set_freq_divider(uint16_t); - - /** Set mode to one-shot, and frequency-divider to t */ - static void oneshot(uint16_t t); - - /** Read back the PIT status from hardware */ - static uint8_t read_back(uint8_t channel); - - - /** A timer is a handler and an expiration time (interval). - @todo The timer also keeps a pre-computed rdtsc-value, which is currently unused.*/ - class Timer { + /** + Programmable Interval Timer class. A singleton. + + @TODO + ...It has timer-functionality, which should probably be super-classed, + so that i.e. the HPET could be used with the same interface. + */ + class PIT{ public: - enum Type { ONE_SHOT, REPEAT, REPEAT_WHILE} type_; + + using timeout_handler = delegate; + using repeat_condition = std::function; + + /** A timer is a handler and an expiration time (interval). + @todo The timer also keeps a pre-computed rdtsc-value, which is currently unused.*/ + class Timer { + public: + enum Type { ONE_SHOT, REPEAT, REPEAT_WHILE} type_; + + Timer() = delete; + Timer(Type, timeout_handler, std::chrono::milliseconds, repeat_condition = forever); + Timer(const Timer&) = default; + Timer(Timer&&) = default; + Timer& operator=(Timer&) = default; + Timer& operator=(Timer&&) = default; + virtual ~Timer() = default; + + inline Type type(){ return type_; } + inline std::chrono::milliseconds interval(){ return interval_; } + inline uint64_t start() { return timestamp_start_; } + inline uint64_t end() { return timestamp_end_; } + inline void setStart(uint64_t s) { timestamp_start_ = s; } + inline void setEnd(uint64_t e) { timestamp_end_ = e; } + inline timeout_handler handler(){ return handler_; } + inline const repeat_condition cond() { return cond_; } + inline uint32_t id(){ return id_; } + + private: + static uint32_t timers_count_; + uint32_t id_ = 0; + timeout_handler handler_; + uint64_t timestamp_start_; + uint64_t timestamp_end_; + std::chrono::milliseconds interval_; + + /* This Could be a reference in the default case of "forever", but then the + case of a normal lambda being passed in, the user would have to be in charge + of storage. */ + const repeat_condition cond_; + }; + + + + using Timer_iterator = std::multimap::iterator; + + /** Create a one-shot timer. + @param ms: Expiration time. Compatible with all std::chrono durations. + @param handler: A delegate or function to be called on timeout. */ + Timer_iterator onTimeout(std::chrono::milliseconds ms, timeout_handler handler); + + static Timer_iterator on_timeout(double sec, timeout_handler handler) { + return instance().onTimeout(std::chrono::milliseconds((unsigned)(sec * 1000)), handler); + } + + /** Create a repeating timer. + @param ms: Expiration time. Compatible with all std::chrono durations. + @param handler: A delegate or function to be called every ms interval. + @param cond: The timer ends when cond() returns false. Default to true. */ + Timer_iterator onRepeatedTimeout(std::chrono::milliseconds ms, + timeout_handler handler, + repeat_condition cond = forever); + + /** Stop a timer. + @param it: A valid iterator to timer */ + void stop_timer(Timer_iterator it); - Timer() = delete; - Timer(Type, timeout_handler, std::chrono::milliseconds, repeat_condition = forever); - Timer(const Timer&) = default; - Timer(Timer&&) = default; - Timer& operator=(Timer&) = default; - Timer& operator=(Timer&&) = default; - virtual ~Timer() = default; + static void stop(Timer_iterator it) + { instance().stop_timer(it); } - inline Type type(){ return type_; } - inline std::chrono::milliseconds interval(){ return interval_; } - inline uint64_t start() { return timestamp_start_; } - inline uint64_t end() { return timestamp_end_; } - inline void setStart(uint64_t s) { timestamp_start_ = s; } - inline void setEnd(uint64_t e) { timestamp_end_ = e; } - inline timeout_handler handler(){ return handler_; } - inline const repeat_condition cond() { return cond_; } - inline uint32_t id(){ return id_; } - + inline size_t active_timers() { return timers_.size(); } + + /** No copy or move. The OS owns one instance forever. */ + PIT(PIT&) = delete; + PIT(PIT&&) = delete; + + /** Get the (single) instance. */ + static PIT& instance() { + static PIT instance_; + return instance_; + }; + + /** Initialize the hardware. */ + static void init(); + + /** The constant frequency of the PIT-hardware, before frequency dividers */ + static constexpr MHz frequency() { return frequency_; } + + static inline MHz current_frequency(){ return frequency() / current_freq_divider_; } + + /** Estimate cpu frequency based on the fixed PIT frequency and rdtsc. + @Note This is an asynchronous function. Once finished the result can be + fetched by CPUFrequency() (below) */ + static void estimateCPUFrequency(); + + /** Get the last estimated CPU frequency. May trigger frequency sampling */ + static MHz CPUFrequency(); + + private: - static uint32_t timers_count_; - uint32_t id_ = 0; - timeout_handler handler_; - uint64_t timestamp_start_; - uint64_t timestamp_end_; - std::chrono::milliseconds interval_; - - /* This Could be a reference in the default case of "forever", but then the - case of a normal lambda being passed in, the user would have to be in charge - of storage. */ - const repeat_condition cond_; + // Default repeat-condition + static std::function forever; + + enum Mode { ONE_SHOT = 0, + HW_ONESHOT = 1 << 1, + RATE_GEN = 2 << 1, + SQ_WAVE = 3 << 1, + SW_STROBE = 4 << 1, + HW_STROBE = 5 << 1, + NONE = 256}; + + // The PIT-chip runs at this fixed frequency (in MHz) , according to OSDev.org */ + static constexpr MHz frequency_ = MHz(14.31818 / 12); + + /** Disable regular timer interrupts- which are turned on at boot-time. */ + static void disable_regular_interrupts(); + + /** The default (soft)handler for timer interrupts */ + void irq_handler(); + + // Private constructor / destructor. It's a singleton. + PIT(); + ~PIT(); + + + // State-keeping + static Mode temp_mode_; + static uint16_t temp_freq_divider_; + static uint8_t status_byte_; + static uint16_t current_freq_divider_; + static Mode current_mode_; + static uint64_t IRQ_counter_; + + + // The closest we can get to a millisecond interval, with the PIT-frequency + static constexpr uint16_t millisec_interval = KHz(frequency_).count(); + + // Count the "milliseconds" + static uint64_t millisec_counter; + + // Access mode bits are bits 4- and 5 in the Mode register + enum AccessMode { LATCH_COUNT = 0x0, LO_ONLY=0x10, HI_ONLY=0x20, LO_HI=0x30 }; + + /** Physically set the PIT-mode */ + static void set_mode(Mode); + + /** Physiclally set the PIT frequency divider */ + static void set_freq_divider(uint16_t); + + /** Set mode to one-shot, and frequency-divider to t */ + static void oneshot(uint16_t t); + + /** Read back the PIT status from hardware */ + static uint8_t read_back(uint8_t channel); + + + /** A map of timers. + @note {Performance: We take advantage of the fact that std::map have sorted keys. + * Timers soonest to expire are in the front, so we only iterate over those + * Deletion of finished timers in amortized constant time, via iterators + * Timer insertion is log(n) } + @note This is why we want to instantiate PIT, and why it's a singleton: + If you don't use PIT-timers, you won't pay for them. */ + std::multimap timers_; + + /** Queue the timer. This will update timestamps in the timer */ + Timer_iterator start_timer(Timer t, std::chrono::milliseconds); + }; - - /** A map of timers. - @note {Performance: We take advantage of the fact that std::map have sorted keys. - * Timers soonest to expire are in the front, so we only iterate over those - * Deletion of finished timers in amortized constant time, via iterators - * Timer insertion is log(n) } - @note This is why we want to instantiate PIT, and why it's a singleton: - If you don't use PIT-timers, you won't pay for them. */ - std::multimap timers_; - - /** Queue the timer. This will update timestamps in the timer */ - void start_timer(Timer t, std::chrono::milliseconds); - -}; } //< namespace hw diff --git a/api/hw/serial.hpp b/api/hw/serial.hpp new file mode 100644 index 0000000000..1bdc4bdda0 --- /dev/null +++ b/api/hw/serial.hpp @@ -0,0 +1,88 @@ +// This file is a part of the IncludeOS unikernel - www.includeos.org +// +// Copyright 2015 Oslo and Akershus University College of Applied Sciences +// and Alfred Bratterud +// +// 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. + +#ifndef HW_SERIAL_HPP +#define HW_SERIAL_HPP + +//#define DEBUG + +#include +#include +#include +#include + +namespace hw{ + + class Serial { + + public: + + /** On Data handler. Return value indicates if the buffer should be flushed **/ + using on_data_handler = delegate; + using on_string_handler = delegate; + + using irq_delg = IRQ_manager::irq_delegate; + + template + static Serial& port(){ + static Serial s{PORT}; + return s; + } + + void on_data(on_data_handler del); + void on_readline(on_string_handler del, char delim = '\r'); + + void enable_interrupt(); + void disable_interrupt(); + + char read(); + void write(char c); + int received(); + int is_transmit_empty(); + + // We don't want copies + Serial( Serial& ) = delete; + Serial( Serial&& ) = delete; + Serial& operator=(Serial&) = delete; + Serial operator=(Serial&&) = delete; + + void init(); + + private: + + Serial(int port); + static constexpr uint16_t ports_[] {0x3F8, 0x2F8, 0x3E8, 0x2E8 }; + static constexpr uint8_t irqs_[] {4, 3, 15, 15 }; + + //static const char default_newline = '\r'; + char newline = '\r'; //default_newline; + + int nr_{0}; + int port_{0}; + uint8_t irq_{4}; + std::string buf{}; + + on_data_handler on_data_ = [](char c){ debug("Default on_data: %c \n", c); (void)c; }; + on_string_handler on_readline_ = [](std::string s) { (void)s; }; + + void irq_handler_ (); + void readline_handler_(char c); + }; + +} + +#endif diff --git a/api/kernel/irq_manager.hpp b/api/kernel/irq_manager.hpp index d712287d91..1e2bcdc8bb 100644 --- a/api/kernel/irq_manager.hpp +++ b/api/kernel/irq_manager.hpp @@ -23,23 +23,6 @@ #include "os.hpp" #include "../hw/pic.hpp" -// IDT Type flags - -// Bits 0-3, "type" (VALUES) -const char TRAP_GATE {0xf}; -const char TASK_GATE {0x5}; -const char INT_GATE {0xe}; - -// Bit 4, "Storage segment" (BIT NUMBER) -const char BIT_STOR_SEG {0x10}; //=0 for trap gates - -//Bits 5-6, "Protection level" (BIT NUMBER) -const char BIT_DPL1 {0x20}; -const char BIT_DPL2 {0x40}; - -//Bit 7, "Present" (BIT NUMBER) -const char BIT_PRESENT {static_cast(0x80)}; - // From osdev struct IDTDescr { uint16_t offset_1; // offset bits 0..15 @@ -71,21 +54,22 @@ extern "C" { NOTES: * All IRQ-callbacks are in charge of calling End-Of-Interrupt - eoi. - Why? Because this makes it possible to prevent further interrupts until - a condition of your choice is met. And, interrupts are costly as they - always cause vm-exit. + Why? Because this makes it possible to prevent further interrupts until + a condition of your choice is met. And, interrupts are costly as they + always cause vm-exit. * IRQ-numbering: 0 or 32? - @TODO: Remove all dependencies on old SanOS code. In particular, eoi is now in global scope - - - */ +*/ class IRQ_manager { public: using irq_delegate = delegate; + static constexpr uint8_t irq_base = 32; + static constexpr uint8_t irq_lines = 64; + + /** * Enable an IRQ line * @@ -106,7 +90,7 @@ class IRQ_manager { * Failure to do so will keep the interrupt from firing and cause a * stack overflow or similar badness. * } - */ + */ static void set_handler(uint8_t irq, void(*function_addr)()); /** Get handler from inside the IDT. */ @@ -116,7 +100,7 @@ class IRQ_manager { * Subscribe to an IRQ * @param irq: The IRQ to subscribe to - * @param del: A delegate to attach to the IRQ DPC-system + * @param del: A delegate to attach to the IRQ DPC-system * The delegate will be called a.s.a.p. after @param irq gets triggered * @@ -132,7 +116,7 @@ class IRQ_manager { * Get the current subscriber of an IRQ-line * * @param irq: The IRQ to get subscriber for - */ + */ static irq_delegate get_subscriber(uint8_t irq); /** @@ -148,20 +132,30 @@ class IRQ_manager { */ static void eoi(uint8_t irq); + static inline void register_interrupt(uint8_t i){ + irq_pending_ |= (1 << i); + __sync_fetch_and_add(&irq_counters_[i],1); + debug(" IRQ %i Pending: 0x%ix. Count: %i\n", i, + irq_pending_, irq_counters_[i]); + } + + private: static unsigned int irq_mask; static int timer_interrupts; - static IDTDescr idt[256]; + static IDTDescr idt[irq_lines]; static const char default_attr {static_cast(0x8e)}; static const uint16_t default_sel {0x8}; static bool idt_is_set; /** bit n set means IRQ n has fired since last check */ //static irq_bitfield irq_pending; - static irq_bitfield irq_subscriptions; + static irq_bitfield irq_subscriptions_; - static void(*irq_subscribers[sizeof(irq_bitfield)*8])(); - static irq_delegate irq_delegates[sizeof(irq_bitfield)*8]; + static void(*irq_subscribers_[sizeof(irq_bitfield)*8])(); + static irq_delegate irq_delegates_[sizeof(irq_bitfield)*8]; + static uint32_t irq_counters_[32]; + static uint32_t irq_pending_; /** STI */ static void enable_interrupts(); @@ -175,9 +169,9 @@ class IRQ_manager { * Use "set_handler" for a simpler version using defaults */ static void create_gate(IDTDescr* idt_entry, - void (*function_addr)(), - uint16_t segment_sel, - char attributes); + void (*function_addr)(), + uint16_t segment_sel, + char attributes); /** The OS will call the following : */ friend class OS; @@ -188,6 +182,7 @@ class IRQ_manager { /** Notify all delegates waiting for interrupts */ static void notify(); + }; //< IRQ_manager #endif //< KERNEL_IRQ_MANAGER_HPP diff --git a/api/kernel/os.hpp b/api/kernel/os.hpp index 2dcee64db7..8f9a99c8ca 100644 --- a/api/kernel/os.hpp +++ b/api/kernel/os.hpp @@ -6,9 +6,9 @@ // 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. @@ -26,7 +26,8 @@ #include -#include "../hw/pit.hpp" +#include +namespace hw{ class Serial; } /** * The entrypoint for OS services @@ -34,33 +35,33 @@ * @note For device access, see Dev */ class OS { -public: +public: using rsprint_func = delegate; - + /* Get the version of the os */ static inline std::string version() { return std::string(OS_VERSION); } - + /** Clock cycles since boot. */ static inline uint64_t cycles_since_boot() { uint64_t ret; __asm__ volatile ("rdtsc":"=A"(ret)); return ret; } - + /** Uptime in seconds. */ static double uptime(); - + /** * Write a cstring to serial port. @todo Should be moved to Dev::serial(n). * * @param ptr: The string to write to serial port - */ + */ static size_t rsprint(const char* ptr); static size_t rsprint(const char* ptr, const size_t len); - + /** - * Write a character to serial port. @todo Should be moved Dev::serial(n) + * Write a character to serial port. * * @param c: The character to print to serial port */ @@ -68,7 +69,7 @@ class OS { /** * Write to serial port with rswrite. - */ + */ static void default_rsprint(const char*, size_t); /** Start the OS. @todo Should be `init()` - and not accessible from ABI */ @@ -81,29 +82,44 @@ class OS { * we'll stay asleep. */ static void halt(); - + /** * Set handler for serial output. */ static void set_rsprint(rsprint_func func) { rsprint_handler_ = func; } - -private: + + /** Memory page helpers */ + static inline constexpr uint32_t page_size() { + return 4096; + } + static inline constexpr uint32_t page_nr_from_addr(uint32_t x){ + return x >> page_shift_; + } + static inline constexpr uint32_t base_from_page_nr(uint32_t x){ + return x << page_shift_; + } + +private: + static const int page_shift_ = 12; + /** Indicate if the OS is running. */ static bool power_; - + /** The main event loop. Check interrupts, timers etc., and do callbacks. */ static void event_loop(); - + static MHz cpu_mhz_; - + static rsprint_func rsprint_handler_; + static hw::Serial& com1; + // Prohibit copy and move operations OS(OS&) = delete; OS(OS&&) = delete; - + // Prohibit construction OS() = delete; }; //< OS diff --git a/api/kernel/service.hpp b/api/kernel/service.hpp index 84f57f0e2f..e4c1c2e1fe 100644 --- a/api/kernel/service.hpp +++ b/api/kernel/service.hpp @@ -26,7 +26,7 @@ extern "C" const char* service_name__; * This is where you take over * * The service gets started whenever the OS is done initializing -*/ + */ class Service { public: /** diff --git a/api/kernel/syscalls.hpp b/api/kernel/syscalls.hpp index 8d2d025c28..57b0b58cfc 100644 --- a/api/kernel/syscalls.hpp +++ b/api/kernel/syscalls.hpp @@ -6,9 +6,9 @@ // 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. @@ -23,6 +23,7 @@ extern "C" { int kill(pid_t pid, int sig); void panic(const char* why) __attribute__((noreturn)); + void default_exit() __attribute__((noreturn)); } #endif //< KERNEL_SYSCALLS_HPP diff --git a/api/kernel/terminal.hpp b/api/kernel/terminal.hpp new file mode 100644 index 0000000000..79257a620b --- /dev/null +++ b/api/kernel/terminal.hpp @@ -0,0 +1,112 @@ +// This file is a part of the IncludeOS unikernel - www.includeos.org +// +// Copyright 2015 Oslo and Akershus University College of Applied Sciences +// and Alfred Bratterud +// +// 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. + +#ifndef KERNEL_BTERM_HPP +#define KERNEL_BTERM_HPP + +#include +#include +#include +#include +#include + +namespace fs +{ + class Disk; +} +namespace hw +{ + class Serial; +} + +struct Command +{ + using main_func = std::function&)>; + + Command(const std::string& descr, main_func func) + : desc(descr), main(func) {} + + std::string desc; + main_func main; +}; + +class Terminal +{ +public: + using Connection_ptr = std::shared_ptr; + using Disk_ptr = std::shared_ptr; + enum + { + NUL = 0, + BELL = 7, + BS = 8, + HTAB = 9, + LF = 10, + VTAB = 11, + FF = 12, + CR = 13 + }; + + using on_write_func = std::function; + + Terminal(Connection_ptr); + Terminal(hw::Serial& serial); + + template + void add(const std::string& command, + Args&&... args) + { + commands.emplace(std::piecewise_construct, + std::forward_as_tuple(command), + std::forward_as_tuple(args...)); + } + + template + void write(const char* str, Args&&... args) + { + char buffer[1024]; + int bytes = snprintf(buffer, 1024, str, args...); + + on_write(buffer, bytes); + } + + std::function on_exit { [] {} }; + + /// + void add_disk_commands(Disk_ptr disk); + +private: + Terminal(); + + void command(uint8_t cmd); + void option(uint8_t option, uint8_t cmd); + void read(const char* buf, size_t len); + void run(const std::string& cmd); + void add_basic_commands(); + void intro(); + void prompt(); + + on_write_func on_write; + + bool iac; + bool newline; + uint8_t subcmd; + std::string buffer; + std::map commands; +}; + +#endif diff --git a/api/kernel/vga.hpp b/api/kernel/vga.hpp index ff64d37a5d..e807bed8eb 100644 --- a/api/kernel/vga.hpp +++ b/api/kernel/vga.hpp @@ -55,12 +55,18 @@ class ConsoleVGA { static const size_t VGA_WIDTH {80}; static const size_t VGA_HEIGHT {25}; -private: void write(char) noexcept; void putEntryAt(const char, const uint8_t, const size_t, const size_t) noexcept; void putEntryAt(const char, const size_t, const size_t) noexcept; + void setCursorAt(const size_t, const size_t) noexcept; void increment(int) noexcept; void newline() noexcept; + inline void set_color(vga_color c) + { color = c; }; + +private: + static const uint16_t DEFAULT_ENTRY; + void put(uint16_t, size_t, size_t) noexcept; size_t row; size_t column; diff --git a/api/memdisk b/api/memdisk index 7f9635d0ac..c05f5dcc93 100644 --- a/api/memdisk +++ b/api/memdisk @@ -22,21 +22,14 @@ #include "fs/disk.hpp" #include "fs/memdisk.hpp" -#include "fs/fat.hpp" // FAT filesystem namespace fs { - // describe a disk with a FAT filesystem - using FatDisk = Disk; - // its not really a shared memdisk right now, - // but we are only using this in conjunction with - // new_shared_memdisk() which very likely contains FAT - using MountedDisk = std::shared_ptr; - - inline MountedDisk new_shared_memdisk() + // new_shared_memdisk() very likely contains FAT + inline Disk_ptr new_shared_memdisk() { static MemDisk device; - return std::make_shared (device); + return std::make_shared (device); } } diff --git a/api/net/arp.hpp b/api/net/arp.hpp deleted file mode 100644 index b0c81c1c2b..0000000000 --- a/api/net/arp.hpp +++ /dev/null @@ -1,154 +0,0 @@ -// This file is a part of the IncludeOS unikernel - www.includeos.org -// -// Copyright 2015 Oslo and Akershus University College of Applied Sciences -// and Alfred Bratterud -// -// 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. - -#pragma once -#ifndef NET_ARP_HPP -#define NET_ARP_HPP - -#include -#include - -#include -#include - -namespace net { - -class PacketArp; - -/** ARP manager, including an ARP-Cache. */ -class Arp { -private: - /** ARP cache expires after cache_exp_t_ seconds */ - static constexpr uint16_t cache_exp_t_ {60 * 60 * 12}; - - /** Cache entries are just MAC's and timestamps */ - struct cache_entry { - Ethernet::addr mac_; - uint64_t timestamp_; - - /** Map needs empty constructor (we have no emplace yet) */ - cache_entry() noexcept = default; - - cache_entry(Ethernet::addr mac) noexcept - : mac_(mac), timestamp_(OS::uptime()) {} - - cache_entry(const cache_entry& cpy) noexcept - : mac_(cpy.mac_), timestamp_(cpy.timestamp_) {} - - void update() noexcept { timestamp_ = OS::uptime(); } - }; //< struct cache_entry - - using Cache = std::map; - using PacketQueue = std::map; -public: - /** - * You can assign your own ARP-resolution delegate - * - * We're doing this to keep the HĂ„rek Haugerud mapping (HH_MAP) - */ - using Arp_resolver = delegate; - - enum Opcode { H_request = 0x100, H_reply = 0x200 }; - - /** Arp opcodes (Big-endian) */ - static constexpr uint16_t H_htype_eth {0x0100}; - static constexpr uint16_t H_ptype_ip4 {0x0008}; - static constexpr uint16_t H_hlen_plen {0x0406}; - - /** Constructor */ - explicit Arp(Inet&) noexcept; - - struct __attribute__((packed)) header { - Ethernet::header ethhdr; // Ethernet header - uint16_t htype; // Hardware type - uint16_t ptype; // Protocol type - uint16_t hlen_plen; // Protocol address length - uint16_t opcode; // Opcode - Ethernet::addr shwaddr; // Source mac - IP4::addr sipaddr; // Source ip - Ethernet::addr dhwaddr; // Target mac - IP4::addr dipaddr; // Target ip - }; - - /** Handle incoming ARP packet. */ - void bottom(Packet_ptr pckt); - - /** Roll your own arp-resolution system. */ - void set_resolver(Arp_resolver ar) - { arp_resolver_ = ar; } - - enum Resolver_name { DEFAULT, HH_MAP }; - - void set_resolver(Resolver_name nm) { - // @TODO: Add HÅREK-mapping here - switch (nm) { - case HH_MAP: - arp_resolver_ = Arp_resolver::from(*this); - break; - default: - arp_resolver_ = Arp_resolver::from(*this); - } - } - - /** Delegate link-layer output. */ - void set_linklayer_out(downstream link) - { linklayer_out_ = link; } - - /** Downstream transmission. */ - void transmit(Packet_ptr); - -private: - Inet& inet_; - - /** Needs to know which mac address to put in header->swhaddr */ - Ethernet::addr mac_; - - /** Outbound data goes through here */ - downstream linklayer_out_; - - /** The ARP cache */ - Cache cache_; - - /** Cache IP resolution. */ - void cache(IP4::addr, Ethernet::addr); - - /** Check if an IP is cached and not expired */ - bool is_valid_cached(IP4::addr); - - /** ARP resolution. */ - Ethernet::addr resolve(IP4::addr); - - void arp_respond(header* hdr_in); - - // two different ARP resolvers - void arp_resolve(Packet_ptr); - void hh_map(Packet_ptr); - - Arp_resolver arp_resolver_ = Arp_resolver::from(*this); - - PacketQueue waiting_packets_; - - /** Add a packet to waiting queue, to be sent when IP is resolved */ - void await_resolution(Packet_ptr, IP4::addr); - - /** Create a default initialized ARP-packet */ - Packet_ptr createPacket(); -}; //< class Arp - -} //< namespace net - -#endif //< NET_ARP_HPP diff --git a/api/net/buffer_store.hpp b/api/net/buffer_store.hpp index 9a6daf04c0..c215e5dcd6 100644 --- a/api/net/buffer_store.hpp +++ b/api/net/buffer_store.hpp @@ -25,76 +25,80 @@ namespace net{ -/** - * Network buffer storage for uniformly sized buffers. - * - * @note : The buffer store is intended to be used by Packet, which is - * a semi-intelligent buffer wrapper, used throughout the IP-stack. - * - * There shouldn't be any need for raw buffers in services. - **/ -class BufferStore { -public: - using buffer_t = uint8_t*; - using release_del = delegate; - - BufferStore(size_t num, size_t bufsize, size_t device_offset); - - /** Free all the buffers **/ - ~BufferStore(); - - /** Get a free buffer */ - buffer_t get_raw_buffer(); - - /** Get a free buffer, offset by device-offset */ - buffer_t get_offset_buffer(); - - /** Return a buffer. */ - void release_raw_buffer(buffer_t b, size_t); - - /** Return a buffer, offset by offset_ bytes from actual buffer. */ - void release_offset_buffer(buffer_t b, size_t); - - /** Get size of a raw buffer **/ - inline size_t raw_bufsize() - { return bufsize_; } - - inline size_t offset_bufsize() - { return bufsize_ - device_offset_; } - - /** @return the total buffer capacity in bytes */ - inline size_t capacity() - { return available_buffers_.size() * bufsize_; } - - /** Check if a buffer belongs here */ - inline bool address_is_from_pool(buffer_t addr) - { return addr >= pool_ and addr < pool_ + (bufcount_ * bufsize_); } - - /** Check if an address is the start of a buffer */ - inline bool address_is_bufstart(buffer_t addr) - { return (addr - pool_) % bufsize_ == 0; } - - /** Check if an address is the start of a buffer */ - inline bool address_is_offset_bufstart(buffer_t addr) - { return (addr - pool_ - device_offset_) % bufsize_ == 0; } -private: - size_t bufcount_; - const size_t bufsize_; - size_t device_offset_; - buffer_t pool_; - std::deque available_buffers_; - - /** Delete move and copy operations **/ - BufferStore(BufferStore&) = delete; - BufferStore(BufferStore&&) = delete; - BufferStore& operator=(BufferStore&) = delete; - BufferStore operator=(BufferStore&&) = delete; - - /** Prohibit default construction **/ - BufferStore() = delete; - - void increaseStorage(); -}; //< class BufferStore + /** + * Network buffer storage for uniformly sized buffers. + * + * @note : The buffer store is intended to be used by Packet, which is + * a semi-intelligent buffer wrapper, used throughout the IP-stack. + * + * There shouldn't be any need for raw buffers in services. + **/ + class BufferStore { + public: + using buffer_t = uint8_t*; + using release_del = delegate; + + BufferStore(size_t num, size_t bufsize, size_t device_offset); + + /** Free all the buffers **/ + ~BufferStore(); + + /** Get a free buffer */ + buffer_t get_raw_buffer(); + + /** Get a free buffer, offset by device-offset */ + buffer_t get_offset_buffer(); + + /** Return a buffer. */ + void release_raw_buffer(buffer_t b, size_t); + + /** Return a buffer, offset by offset_ bytes from actual buffer. */ + void release_offset_buffer(buffer_t b, size_t); + + /** Get size of a raw buffer **/ + inline size_t raw_bufsize() + { return bufsize_; } + + inline size_t offset_bufsize() + { return bufsize_ - device_offset_; } + + /** @return the total buffer capacity in bytes */ + inline size_t capacity() + { return available_buffers_.size() * bufsize_; } + + /** Check if a buffer belongs here */ + inline bool address_is_from_pool(buffer_t addr) + { return addr >= pool_ and addr < pool_ + (bufcount_ * bufsize_); } + + /** Check if an address is the start of a buffer */ + inline bool address_is_bufstart(buffer_t addr) + { return (addr - pool_) % bufsize_ == 0; } + + /** Check if an address is the start of a buffer */ + inline bool address_is_offset_bufstart(buffer_t addr) + { return (addr - pool_ - device_offset_) % bufsize_ == 0; } + + inline size_t buffers_available() + { return available_buffers_.size(); } + + private: + size_t bufcount_; + const size_t bufsize_; + size_t device_offset_; + buffer_t pool_; + std::deque available_buffers_; + + /** Delete move and copy operations **/ + BufferStore(BufferStore&) = delete; + BufferStore(BufferStore&&) = delete; + BufferStore& operator=(BufferStore&) = delete; + BufferStore operator=(BufferStore&&) = delete; + + /** Prohibit default construction **/ + BufferStore() = delete; + + void increaseStorage(); + }; //< class BufferStore } //< namespace net #endif //< NET_BUFFER_STORE_HPP diff --git a/api/net/dhcp/dh4client.hpp b/api/net/dhcp/dh4client.hpp index f97a1362b8..b9f341ab8b 100644 --- a/api/net/dhcp/dh4client.hpp +++ b/api/net/dhcp/dh4client.hpp @@ -15,20 +15,16 @@ // See the License for the specific language governing permissions and // limitations under the License. +#pragma once #ifndef NET_DHCP_DH4CLIENT_HPP #define NET_DHCP_DH4CLIENT_HPP -#define DEBUG +#include #include "../packet.hpp" -#include -#include - namespace net { - template - class Socket; - class UDP; + class UDPSocket; template class Inet; @@ -37,31 +33,37 @@ namespace net { public: using Stack = Inet; - using On_config = delegate; + using config_func = delegate; DHClient() = delete; DHClient(DHClient&) = delete; - DHClient(Stack&); + DHClient(Stack& inet); - Stack& stack; - void negotiate(); // --> offer - inline void on_config(On_config handler){ - config_handler = handler; - } + // negotiate with local DHCP server + void negotiate(double timeout_secs); + + // handler for result of DHCP negotation + // timeout is true if the negotiation timed out + void on_config(config_func handler) + { config_handler = handler; } + + // disable or enable console spam + void set_silent(bool sil) + { this->console_spam = !sil; } private: - void offer(Socket&, const char* data, int len); - void request(Socket&); // --> acknowledge - void acknowledge(const char* data, int len); + void offer(UDPSocket&, const char* data, size_t len); + void request(UDPSocket&); // --> acknowledge + void acknowledge(const char* data, size_t len); - uint32_t xid; - IP4::addr ipaddr, netmask, router, dns_server; - uint32_t lease_time; - On_config config_handler = [](Stack&){ INFO("DHCPv4::On_config","Config complete"); }; + Stack& stack; + uint32_t xid; + IP4::addr ipaddr, netmask, router, dns_server; + uint32_t lease_time; + config_func config_handler; + hw::PIT::Timer_iterator timeout; + bool console_spam; }; - - inline DHClient::DHClient(Stack& inet) - : stack(inet) {} } #endif diff --git a/api/net/dhcp/dhcp4.hpp b/api/net/dhcp/dhcp4.hpp index b1a021232d..d4751d8ee0 100644 --- a/api/net/dhcp/dhcp4.hpp +++ b/api/net/dhcp/dhcp4.hpp @@ -20,7 +20,7 @@ #ifndef NET_DHCP_DHCP4_HPP #define NET_DHCP_DHCP4_HPP -#define DHCP_VEND_LEN 304 +#define DHCP_VEND_LEN 304 #define BOOTP_MIN_LEN 300 #define DHCP_MIN_LEN 548 // some clients silently ignore responses less than 300 bytes diff --git a/api/net/dns/client.hpp b/api/net/dns/client.hpp index 93b61d59c8..d877644c50 100644 --- a/api/net/dns/client.hpp +++ b/api/net/dns/client.hpp @@ -18,8 +18,8 @@ #ifndef NET_DNS_CLIENT_HPP #define NET_DNS_CLIENT_HPP -#include "../inet.hpp" -#include "../ip4.hpp" +#include +#include #include namespace net @@ -30,12 +30,12 @@ namespace net using Stack = Inet; DNSClient(Stack& stk) - : stack(stk) {} + : stack(stk) {} /** * @func a delegate that provides a hostname and its address, which is 0 if the * name @hostname was not found. Note: Test with INADDR_ANY for a 0-address. - **/ + **/ void resolve(IP4::addr dns_server, const std::string& hostname, Stack::resolve_func func); diff --git a/api/net/dns/dns.hpp b/api/net/dns/dns.hpp index d54a11c02a..1b03f90eaa 100644 --- a/api/net/dns/dns.hpp +++ b/api/net/dns/dns.hpp @@ -15,8 +15,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef NET_DNS_HPP -#define NET_DNS_HPP +#ifndef NET_DNS_DNS_HPP +#define NET_DNS_DNS_HPP /** * DNS message @@ -49,7 +49,7 @@ * **/ -#include // UDP headers +#include // IP4::addr #include #include #include @@ -116,14 +116,14 @@ namespace net #pragma pack(pop) enum resp_code - { - NO_ERROR = 0, - FORMAT_ERROR = 1, - SERVER_FAIL = 2, - NAME_ERROR = 3, - NOT_IMPL = 4, // unimplemented feature - OP_REFUSED = 5, // for political reasons - }; + { + NO_ERROR = 0, + FORMAT_ERROR = 1, + SERVER_FAIL = 2, + NAME_ERROR = 3, + NOT_IMPL = 4, // unimplemented feature + OP_REFUSED = 5, // for political reasons + }; typedef std::function* (const std::string&)> lookup_func; @@ -132,18 +132,18 @@ namespace net static std::string question_string(unsigned short type) { switch (type) - { - case DNS_TYPE_A: - return "IPv4 address"; - case DNS_TYPE_ALIAS: - return "Alias"; - case DNS_TYPE_MX: - return "Mail exchange"; - case DNS_TYPE_NS: - return "Name server"; - default: - return "FIXME DNS::question_string(type = " + std::to_string(type) + ")"; - } + { + case DNS_TYPE_A: + return "IPv4 address"; + case DNS_TYPE_ALIAS: + return "Alias"; + case DNS_TYPE_MX: + return "Mail exchange"; + case DNS_TYPE_NS: + return "Name server"; + default: + return "FIXME DNS::question_string(type = " + std::to_string(type) + ")"; + } } class Request @@ -159,10 +159,10 @@ namespace net } IP4::addr getFirstIP4() const { - IP4::addr result{{0}}; if (answers.size()) - result = answers[0].getIP4(); - return result; + return answers[0].getIP4(); + + return IP4::INADDR_ANY; } private: diff --git a/api/net/ethernet.hpp b/api/net/ethernet.hpp index f70c2e855e..8dc1e20c40 100644 --- a/api/net/ethernet.hpp +++ b/api/net/ethernet.hpp @@ -6,9 +6,9 @@ // 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. @@ -25,143 +25,145 @@ namespace net { -/** Ethernet packet handling. */ -class Ethernet { -public: - static constexpr size_t ETHER_ADDR_LEN = 6; - static constexpr size_t MINIMUM_PAYLOAD = 46; - - /** - * Some big-endian ethernet types - * - * From http://en.wikipedia.org/wiki/EtherType - */ - enum ethertype_le { - _ETH_IP4 = 0x0800, - _ETH_ARP = 0x0806, - _ETH_WOL = 0x0842, - _ETH_IP6 = 0x86DD, - _ETH_FLOW = 0x8808, - _ETH_JUMBO = 0x8870 - }; - - /** Little-endian ethertypes. */ - enum ethertype { - ETH_IP4 = 0x8, - ETH_ARP = 0x608, - ETH_WOL = 0x4208, - ETH_IP6 = 0xdd86, - ETH_FLOW = 0x888, - ETH_JUMBO = 0x7088, - ETH_VLAN = 0x81 - }; - - // MAC address - union addr { - uint8_t part[ETHER_ADDR_LEN]; - - struct { - uint16_t minor; - uint32_t major; - } __attribute__((packed)); - - addr& operator=(const addr cpy) noexcept { - minor = cpy.minor; - major = cpy.major; - return *this; - } - - // hex string representation - std::string str() const { - char eth_addr[17]; - sprintf(eth_addr, "%1x:%1x:%1x:%1x:%1x:%1x", - part[0], part[1], part[2], - part[3], part[4], part[5]); - return eth_addr; - } - - /** Check for equality */ - bool operator==(const addr mac) const noexcept - { - return strncmp( - reinterpret_cast(part), - reinterpret_cast(mac.part), - ETHER_ADDR_LEN) == 0; - } - - static const addr MULTICAST_FRAME; - static const addr BROADCAST_FRAME; - - static const addr IPv6mcast_01; - static const addr IPv6mcast_02; - - } __attribute__((packed)); //< union addr - - /** Constructor */ - explicit Ethernet(addr mac) noexcept; - - struct header { - addr dest; - addr src; - unsigned short type; - - } __attribute__((packed)) ; - - /** Bottom upstream input, "Bottom up". Handle raw ethernet buffer. */ - void bottom(Packet_ptr); - - /** Delegate upstream ARP handler. */ - void set_arp_handler(upstream del) - { arp_handler_ = del; } - - upstream get_arp_handler() - { return arp_handler_; } - - /** Delegate upstream IPv4 handler. */ - void set_ip4_handler(upstream del) - { ip4_handler_ = del; } - - /** Delegate upstream IPv4 handler. */ - upstream get_ip4_handler() - { return ip4_handler_; } - - /** Delegate upstream IPv6 handler. */ - void set_ip6_handler(upstream del) - { ip6_handler_ = del; }; - - /** Delegate downstream */ - void set_physical_out(downstream del) - { physical_out_ = del; } - - /** @return Mac address of the underlying device */ - const addr mac() const noexcept - { return mac_; } - - /** Transmit data, with preallocated space for eth.header */ - void transmit(Packet_ptr); - -private: - /** MAC address */ - addr mac_; - - /** Upstream OUTPUT connections */ - upstream ip4_handler_ = [](Packet_ptr){}; - upstream ip6_handler_ = [](Packet_ptr){}; - upstream arp_handler_ = [](Packet_ptr){}; - - /** Downstream OUTPUT connection */ - downstream physical_out_ = [](Packet_ptr){}; - - /* - - +--|IP4|---|ARP|---|IP6|---+ - | | - | Ethernet | - | | - +---------|Phys|-----------+ - - */ -}; //< class Ethernet + /** Ethernet packet handling. */ + class Ethernet { + public: + static constexpr size_t ETHER_ADDR_LEN = 6; + static constexpr size_t MINIMUM_PAYLOAD = 46; + + /** + * Some big-endian ethernet types + * + * From http://en.wikipedia.org/wiki/EtherType + */ + enum ethertype_le { + _ETH_IP4 = 0x0800, + _ETH_ARP = 0x0806, + _ETH_WOL = 0x0842, + _ETH_IP6 = 0x86DD, + _ETH_FLOW = 0x8808, + _ETH_JUMBO = 0x8870 + }; + + /** Little-endian ethertypes. */ + enum ethertype { + ETH_IP4 = 0x8, + ETH_ARP = 0x608, + ETH_WOL = 0x4208, + ETH_IP6 = 0xdd86, + ETH_FLOW = 0x888, + ETH_JUMBO = 0x7088, + ETH_VLAN = 0x81 + }; + + // MAC address + union addr { + uint8_t part[ETHER_ADDR_LEN]; + + struct { + uint16_t minor; + uint32_t major; + } __attribute__((packed)); + + addr& operator=(const addr cpy) noexcept { + minor = cpy.minor; + major = cpy.major; + return *this; + } + + // hex string representation + std::string str() const { + char eth_addr[17]; + sprintf(eth_addr, "%1x:%1x:%1x:%1x:%1x:%1x", + part[0], part[1], part[2], + part[3], part[4], part[5]); + return eth_addr; + } + + /** Check for equality */ + bool operator==(const addr mac) const noexcept + { + return strncmp( + reinterpret_cast(part), + reinterpret_cast(mac.part), + ETHER_ADDR_LEN) == 0; + } + + static const addr MULTICAST_FRAME; + static const addr BROADCAST_FRAME; + + static const addr IPv6mcast_01; + static const addr IPv6mcast_02; + + } __attribute__((packed)); //< union addr + + /** Constructor */ + explicit Ethernet(addr mac) noexcept; + + struct header { + addr dest; + addr src; + unsigned short type; + + } __attribute__((packed)) ; + + using trailer = uint32_t; + + /** Bottom upstream input, "Bottom up". Handle raw ethernet buffer. */ + void bottom(Packet_ptr); + + /** Delegate upstream ARP handler. */ + void set_arp_handler(upstream del) + { arp_handler_ = del; } + + upstream get_arp_handler() + { return arp_handler_; } + + /** Delegate upstream IPv4 handler. */ + void set_ip4_handler(upstream del) + { ip4_handler_ = del; } + + /** Delegate upstream IPv4 handler. */ + upstream get_ip4_handler() + { return ip4_handler_; } + + /** Delegate upstream IPv6 handler. */ + void set_ip6_handler(upstream del) + { ip6_handler_ = del; }; + + /** Delegate downstream */ + void set_physical_out(downstream del) + { physical_out_ = del; } + + /** @return Mac address of the underlying device */ + const addr mac() const noexcept + { return mac_; } + + /** Transmit data, with preallocated space for eth.header */ + void transmit(Packet_ptr); + + private: + /** MAC address */ + addr mac_; + + /** Upstream OUTPUT connections */ + upstream ip4_handler_ = [](Packet_ptr){}; + upstream ip6_handler_ = [](Packet_ptr){}; + upstream arp_handler_ = [](Packet_ptr){}; + + /** Downstream OUTPUT connection */ + downstream physical_out_ = [](Packet_ptr){}; + + /* + + +--|IP4|---|ARP|---|IP6|---+ + | | + | Ethernet | + | | + +---------|Phys|-----------+ + + */ + }; //< class Ethernet } // namespace net #endif //< NET_ETHERNET_HPP diff --git a/api/net/inet.hpp b/api/net/inet.hpp index bb7db1794c..17431d00b4 100644 --- a/api/net/inet.hpp +++ b/api/net/inet.hpp @@ -6,9 +6,9 @@ // 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. @@ -21,45 +21,53 @@ #include namespace net { - -class TCP; -class UDP; -class DHClient; - -/** An abstract IP-stack interface */ -template -class Inet { -public: - using Stack = Inet; - - template - using resolve_func = delegate; - - virtual typename IPV::addr ip_addr() = 0; - virtual typename IPV::addr netmask() = 0; - virtual typename IPV::addr router() = 0; - virtual typename LINKLAYER::addr link_addr() = 0; - - virtual LINKLAYER& link() = 0; - virtual IPV& ip_obj() = 0; - virtual TCP& tcp() = 0; - virtual UDP& udp() = 0; - - virtual std::shared_ptr dhclient() = 0; - - virtual uint16_t MTU() const = 0; - - virtual Packet_ptr createPacket(size_t size) = 0; - - virtual void resolve(const std::string& hostname, resolve_func func) = 0; - - virtual void set_dns_server(typename IPV::addr server) = 0; - - virtual void network_config(typename IPV::addr ip, - typename IPV::addr nmask, - typename IPV::addr router, - typename IPV::addr dnssrv) = 0; -}; //< class Inet + + class TCP; + class UDP; + class DHClient; + + /** An abstract IP-stack interface */ + template + class Inet { + public: + using Stack = Inet; + + template + using resolve_func = delegate; + + virtual typename IPV::addr ip_addr() = 0; + virtual typename IPV::addr netmask() = 0; + virtual typename IPV::addr router() = 0; + virtual typename LINKLAYER::addr link_addr() = 0; + + virtual LINKLAYER& link() = 0; + virtual IPV& ip_obj() = 0; + virtual TCP& tcp() = 0; + virtual UDP& udp() = 0; + + virtual constexpr uint16_t MTU() const = 0; + + virtual Packet_ptr createPacket(size_t size) = 0; + + virtual void resolve(const std::string& hostname, resolve_func func) = 0; + + virtual void set_dns_server(typename IPV::addr server) = 0; + + virtual void network_config(typename IPV::addr ip, + typename IPV::addr nmask, + typename IPV::addr router, + typename IPV::addr dnssrv) = 0; + + /** Event triggered when there are available buffers in the transmit queue */ + virtual void on_transmit_queue_available(transmit_avail_delg del) = 0; + + /** Number of packets the transmit queue has room for */ + virtual size_t transmit_queue_available() = 0; + + /** Number of buffers available in the bufstore */ + virtual size_t buffers_available() = 0; + + }; //< class Inet } //< namespace net #endif diff --git a/api/net/inet4.hpp b/api/net/inet4.hpp index 4359c54caa..e07f8d1f6d 100644 --- a/api/net/inet4.hpp +++ b/api/net/inet4.hpp @@ -6,9 +6,9 @@ // 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. @@ -23,102 +23,102 @@ #include #include "inet.hpp" #include "ethernet.hpp" -#include "arp.hpp" -#include "ip4.hpp" +#include "ip4/arp.hpp" +#include "ip4/ip4.hpp" #include "ip4/udp.hpp" +#include "ip4/icmpv4.hpp" #include "dns/client.hpp" #include "tcp.hpp" -#include "dhcp/dh4client.hpp" #include -#include "ip4/icmpv4.hpp" - namespace net { - + + class DHClient; + /** A complete IP4 network stack */ template class Inet4 : public Inet{ public: - - Ethernet::addr link_addr() override + + Ethernet::addr link_addr() override { return eth_.mac(); } - - IP4::addr ip_addr() override + + IP4::addr ip_addr() override { return ip4_addr_; } - - IP4::addr netmask() override + + IP4::addr netmask() override { return netmask_; } - - IP4::addr router() override + + IP4::addr router() override { return router_; } - + Ethernet& link() override - { return eth_; } - - inline IP4& ip_obj() override + { return eth_; } + + IP4& ip_obj() override { return ip4_; } - + /** Get the TCP-object belonging to this stack */ - inline TCP& tcp() override { debug(" Returning tcp-reference to %p \n",&tcp_); return tcp_; } - + TCP& tcp() override { return tcp_; } + /** Get the UDP-object belonging to this stack */ - inline UDP& udp() override { return udp_; } + UDP& udp() override { return udp_; } /** Get the DHCP client (if any) */ - inline std::shared_ptr dhclient() override { return dhcp_; } - + auto dhclient() { return dhcp_; } + /** Create a Packet, with a preallocated buffer. - @param size : the "size" reported by the allocated packet. - @note as of v0.6.3 this has no effect other than to force the size to be - set explicitly by the caller. - @todo make_shared will allocate with new. This is fast in IncludeOS, - (no context switch for sbrk) but consider overloading operator new. + @param size : the "size" reported by the allocated packet. + @note as of v0.6.3 this has no effect other than to force the size to be + set explicitly by the caller. + @todo make_shared will allocate with new. This is fast in IncludeOS, + (no context switch for sbrk) but consider overloading operator new. */ - inline Packet_ptr createPacket(size_t size) override { + virtual Packet_ptr createPacket(size_t size) override { // Create a release delegate, for returning buffers auto release = BufferStore::release_del::from - (nic_.bufstore()); + (nic_.bufstore()); // Create the packet, using buffer and . - return std::make_shared(bufstore_.get_offset_buffer(), - bufstore_.offset_bufsize(), size, release); + return std::make_shared(bufstore_.get_offset_buffer(), + bufstore_.offset_bufsize(), size, release); } - + // We have to ask the Nic for the MTU - virtual inline uint16_t MTU() const override + virtual uint16_t MTU() const override { return nic_.MTU(); } - inline auto available_capacity() - { return bufstore_.capacity(); } - /** * @func a delegate that provides a hostname and its address, which is 0 if the * name @hostname was not found. Note: Test with INADDR_ANY for a 0-address. - **/ - inline virtual void - resolve(const std::string& hostname, - resolve_func func) override + **/ + virtual void resolve(const std::string& hostname, + resolve_func func) override { dns.resolve(this->dns_server, hostname, func); } - - inline virtual void - set_dns_server(IP4::addr server) override + + virtual void set_dns_server(IP4::addr server) override { this->dns_server = server; } - + + // handler called after the network successfully, or + // unsuccessfully negotiated with DHCP-server + // the timeout parameter indicates whether dhcp negotitation failed + void on_config(delegate handler); + /** We don't want to copy or move an IP-stack. It's tied to a device. */ Inet4(Inet4&) = delete; Inet4(Inet4&&) = delete; Inet4& operator=(Inet4) = delete; Inet4 operator=(Inet4&&) = delete; - + /** Initialize with static IP / netmask */ Inet4(hw::Nic& nic, IP4::addr ip, IP4::addr netmask); - + /** Initialize with DHCP */ - Inet4(hw::Nic& nic); - + Inet4(hw::Nic& nic, double timeout = 10.0); + virtual void network_config(IP4::addr addr, IP4::addr nmask, IP4::addr router, IP4::addr dns) override { @@ -129,15 +129,32 @@ namespace net { this->dns_server = dns; } - private: + // register a callback for receiving signal on free packet-buffers + virtual void + on_transmit_queue_available(transmit_avail_delg del) override { + tqa.push_back(del); + } + + virtual size_t transmit_queue_available() override { + return nic_.transmit_queue_available(); + } + + virtual size_t buffers_available() override { + return nic_.buffers_available(); + } + + private: + inline void process_sendq(size_t); + // delegates registered to get signalled about free packets + std::vector tqa; IP4::addr ip4_addr_; IP4::addr netmask_; IP4::addr router_; IP4::addr dns_server; - + // This is the actual stack - hw::Nic& nic_; + hw::Nic& nic_; Ethernet eth_; Arp arp_; IP4 ip4_; @@ -146,7 +163,7 @@ namespace net { TCP tcp_; // we need this to store the cache per-stack DNSClient dns; - + std::shared_ptr dhcp_{}; BufferStore& bufstore_; }; diff --git a/api/net/inet4.inc b/api/net/inet4.inc index d2e7fdc161..03936bc3e7 100644 --- a/api/net/inet4.inc +++ b/api/net/inet4.inc @@ -1,86 +1,140 @@ //-*- C++ -*- #define DEBUG #include +#include "dhcp/dh4client.hpp" namespace net { - template + template inline Inet4::Inet4(hw::Nic& nic, IP4::addr ip, IP4::addr netmask) - : ip4_addr_(ip), netmask_(netmask), router_(IP4::INADDR_ANY), - nic_(nic), eth_(nic.mac()), arp_(*this), ip4_(*this), - icmp_(*this), udp_(*this), tcp_(*this), dns(*this), + : ip4_addr_(ip), netmask_(netmask), router_(IP4::INADDR_ANY), + nic_(nic), eth_(nic.mac()), arp_(*this), ip4_(*this), + icmp_(*this), udp_(*this), tcp_(*this), dns(*this), bufstore_(nic.bufstore()) { - debug(" Constructor. TCP @ %p has %i open ports. \n", &tcp_, tcp_.openPorts()); + debug(" Constructor. TCP @ %p has %i open ports. \n", &tcp_, tcp_.openPorts()); INFO("Inet4","Bringing up the IP stack"); - - /** Upstream delegates */ + Ensures(sizeof(IP4::addr) == 4); + + /** Upstream delegates */ auto eth_bottom(upstream::from(eth_)); auto arp_bottom(upstream::from(arp_)); auto ip4_bottom(upstream::from(ip4_)); auto icmp4_bottom(upstream::from(icmp_)); auto udp4_bottom(upstream::from(udp_)); auto tcp_bottom(upstream::from(tcp_)); - + /** Upstream wiring */ - + // Packets available + nic.on_transmit_queue_available( + transmit_avail_delg::from, &Inet4::process_sendq>(*this)); + // Phys -> Eth (Later, this will be passed through router) nic.set_linklayer_out(eth_bottom); - + // Eth -> Arp eth_.set_arp_handler(arp_bottom); - + // Eth -> IP4 eth_.set_ip4_handler(ip4_bottom); - + // IP4 -> ICMP ip4_.set_icmp_handler(icmp4_bottom); - + // IP4 -> UDP ip4_.set_udp_handler(udp4_bottom); - + // IP4 -> TCP ip4_.set_tcp_handler(tcp_bottom); - - + /** Downstream delegates */ auto phys_top(downstream ::from,&hw::Nic::transmit>(nic)); auto eth_top(downstream - ::from(eth_)); + ::from(eth_)); auto arp_top(downstream ::from(arp_)); auto ip4_top(downstream ::from(ip4_)); - + /** Downstream wiring. */ - + // ICMP -> IP4 icmp_.set_network_out(ip4_top); - + // UDP4 -> IP4 udp_.set_network_out(ip4_top); - + // TCP -> IP4 tcp_.set_network_out(ip4_top); - // IP4 -> Arp + // IP4 -> Arp ip4_.set_linklayer_out(arp_top); - + // Arp -> Eth arp_.set_linklayer_out(eth_top); - + // Eth -> Phys eth_.set_physical_out(phys_top); } - - template - Inet4::Inet4(hw::Nic& nic) + + template inline + Inet4::Inet4(hw::Nic& nic, double timeout) : Inet4(nic, IP4::INADDR_ANY, IP4::INADDR_ANY) { - INFO("Inet4","Applying DHCP client"); + INFO("Inet4", "Trying DHCP..."); dhcp_ = std::make_shared(*this); - dhcp_->negotiate(); + // 2 second timeout for DHCP-server negotation + dhcp_->negotiate(timeout); + } + + template inline + void Inet4::on_config(delegate handler) { + dhcp_->on_config(handler); } - + + template inline + void Inet4::process_sendq(size_t packets) { + + //////////////////////////////////////////// + // divide up fairly + size_t div = packets / tqa.size(); + + // give each protocol a chance to take + for (size_t i = 0; i < tqa.size(); i++) + tqa[i](div); + + // hand out remaining + for (size_t i = 0; i < tqa.size(); i++) { + div = transmit_queue_available(); + if (!div) break; + // give as much as possible + tqa[i](div); + } + //////////////////////////////////////////// + + /* + size_t list[tqa.size()]; + for (size_t i = 0; i < tqa.size(); i++) + list[i] = tqa[i](0); + + size_t give[tqa.size()] = {0}; + int cp = 0; // current protocol + + // distribute packets one at a time for each protocol + while (packets--) + { + if (list[cp]) + { + // give one packet + give[cp]++; list[cp]--; + } + cp = (cp + 1) % tqa.size(); + } + // hand out several packets per protocol + for (size_t i = 0; i < tqa.size(); i++) + if (give[i]) tqa[i](give[i]); + */ + } + } diff --git a/api/net/inet64.hpp b/api/net/inet64.hpp index 471f603762..b7d9bb804d 100644 --- a/api/net/inet64.hpp +++ b/api/net/inet64.hpp @@ -43,14 +43,14 @@ namespace net { class Inet64 { public: /** Listen to a UDP port. - This is just a simple forwarder. @see UDP::listen. */ + This is just a simple forwarder. @see UDP::listen. */ inline void udp_listen(uint16_t port, UDP::listener l) { _udp.listen(port,l); } /** Send a UDP datagram. - - @note the data buffer is the *whole* ethernet frame, so don't overwrite - headers unless you own them (i.e. you *are* the IP object) */ + + @note the data buffer is the *whole* ethernet frame, so don't overwrite + headers unless you own them (i.e. you *are* the IP object) */ inline int udp_send(std::shared_ptr pckt) { return _udp.transmit(pckt); } @@ -62,7 +62,7 @@ namespace net { } /// send an UDPv6 packet, hopefully (please dont lie!) std::shared_ptr udp6_create( - Ethernet::addr ether_dest, const IP6::addr& ip_dest, UDPv6::port_t port) + Ethernet::addr ether_dest, const IP6::addr& ip_dest, UDPv6::port_t port) { return _udp6.create(ether_dest, ip_dest, port); } @@ -79,13 +79,13 @@ namespace net { } /** Bind an IP and a netmask to a given device. - The function expects the given device to exist.*/ + The function expects the given device to exist.*/ static void ifconfig( - netdev nic, - IP4::addr ip, - IP4::addr netmask, - IP6::addr ip6); + netdev nic, + IP4::addr ip, + IP4::addr netmask, + IP6::addr ip6); inline static IP4::addr ip4(netdev nic) { return _ip4_list[nic]; } @@ -124,15 +124,15 @@ namespace net { /** Don't think we *want* copy construction. - @todo: Fix this with a singleton or something. - */ + @todo: Fix this with a singleton or something. + */ Inet(Inet& UNUSED(cpy)) = delete; Inet(std::vector ips); /** Initialize. For now IP and mac is passed on to Ethernet and Arp. - @todo For now, mac- and IP-addresses are hardcoded here. - They should be user-definable + @todo For now, mac- and IP-addresses are hardcoded here. + They should be user-definable */ Inet(); diff --git a/api/net/inet_common.hpp b/api/net/inet_common.hpp index 723ef02fee..c0ec7ec83b 100644 --- a/api/net/inet_common.hpp +++ b/api/net/inet_common.hpp @@ -6,9 +6,9 @@ // 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. @@ -23,27 +23,30 @@ #include namespace net { -// Packet must be forward declared to avoid circular dependency -// i.e. IP uses Packet, and Packet uses IP headers -class Packet; -class Ethernet; + // Packet must be forward declared to avoid circular dependency + // i.e. IP uses Packet, and Packet uses IP headers + class Packet; + class Ethernet; + + using LinkLayer = Ethernet; -using LinkLayer = Ethernet; + using Packet_ptr = std::shared_ptr; -using Packet_ptr = std::shared_ptr; + // Downstream / upstream delegates + using downstream = delegate; + using upstream = downstream; -// Downstream / upstream delegates -using downstream = delegate; -using upstream = downstream; + // Delegate for signalling available buffers in device transmit queue + using transmit_avail_delg = delegate; -// Compute the internet checksum for the buffer / buffer part provided -uint16_t checksum(void* data, size_t len) noexcept; + // Compute the internet checksum for the buffer / buffer part provided + uint16_t checksum(void* data, size_t len) noexcept; -// View a packet differently based on context -template -inline auto view_packet_as(Packet packet) noexcept { - return std::static_pointer_cast(packet); -} + // View a packet differently based on context + template + inline auto view_packet_as(Packet packet) noexcept { + return std::static_pointer_cast(packet); + } } //< namespace net diff --git a/api/net/ip4.hpp b/api/net/ip4.hpp deleted file mode 100644 index 85905ddadd..0000000000 --- a/api/net/ip4.hpp +++ /dev/null @@ -1,172 +0,0 @@ -// This file is a part of the IncludeOS unikernel - www.includeos.org -// -// Copyright 2015 Oslo and Akershus University College of Applied Sciences -// and Alfred Bratterud -// -// 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. - -#ifndef CLASS_IP4_HPP -#define CLASS_IP4_HPP - -#include -#include - -#include -#include - -namespace net { - -// Default delegate assignments -void ignore_ip4_up(Packet_ptr); -void ignore_ip4_down(Packet_ptr); - -/** IP4 layer */ -class IP4 { -public: - /** Initialize. Sets a dummy linklayer out. */ - explicit IP4(Inet&) noexcept; - - /** Known transport layer protocols. */ - enum proto { IP4_ICMP=1, IP4_UDP=17, IP4_TCP=6 }; - - /** IP4 address representation */ - union __attribute__((packed)) addr { - uint8_t part[4]; - uint32_t whole; - - /** - * NOTE: Constructors - * Can't have them - removes the packed-attribute - */ - - inline addr& operator=(addr cpy) noexcept { - whole = cpy.whole; - return *this; - } - - /** Standard comparison operators */ - inline bool operator==(addr rhs) const noexcept - { return whole == rhs.whole; } - - inline bool operator==(const uint32_t rhs) const noexcept - { return whole == rhs; } - - inline bool operator<(const addr rhs) const noexcept - { return whole < rhs.whole; } - - inline bool operator<(const uint32_t rhs) const noexcept - { return whole < rhs; } - - inline bool operator>(const addr rhs) const noexcept - { return whole > rhs.whole; } - - inline bool operator>(const uint32_t rhs) const noexcept - { return whole > rhs; } - - inline bool operator!=(const addr rhs) const noexcept - { return whole != rhs.whole; } - - inline bool operator!=(const uint32_t rhs) const noexcept - { return whole != rhs; } - - /** x.x.x.x string representation */ - std::string str() const { - char ip_addr[16]; - sprintf(ip_addr, "%1i.%1i.%1i.%1i", - part[0], part[1], part[2], part[3]); - return ip_addr; - } - }; //< union addr - - static const addr INADDR_ANY; - static const addr INADDR_BCAST; - - /** IP4 header representation */ - struct ip_header { - uint8_t version_ihl; - uint8_t tos; - uint16_t tot_len; - uint16_t id; - uint16_t frag_off_flags; - uint8_t ttl; - uint8_t protocol; - uint16_t check; - addr saddr; - addr daddr; - }; - - /** - * The full header including IP - * - * @Note: This might be removed if we decide to isolate layers more - */ - struct full_header { - uint8_t link_hdr[sizeof(typename LinkLayer::header)]; - ip_header ip_hdr; - }; - - /** Upstream: Input from link layer */ - void bottom(Packet_ptr); - - /** Upstream: Outputs to transport layer */ - inline void set_icmp_handler(upstream s) - { icmp_handler_ = s; } - - inline void set_udp_handler(upstream s) - { udp_handler_ = s; } - - inline void set_tcp_handler(upstream s) - { tcp_handler_ = s; } - - /** Downstream: Delegate linklayer out */ - void set_linklayer_out(downstream s) - { linklayer_out_ = s; }; - - /** - * Downstream: Receive data from above and transmit - * - * @note: The following *must be set* in the packet: - * - * * Destination IP - * * Protocol - * - * Source IP *can* be set - if it's not, IP4 will set it - */ - void transmit(Packet_ptr); - - /** Compute the IP4 header checksum */ - uint16_t checksum(ip_header*); - - /** - * \brief - * - * Returns the IPv4 address associated with this interface - **/ - const addr local_ip() const { - return stack_.ip_addr(); - } - -private: - Inet& stack_; - - /** Downstream: Linklayer output delegate */ - downstream linklayer_out_ {ignore_ip4_down}; - - /** Upstream delegates */ - upstream icmp_handler_ {ignore_ip4_up}; - upstream udp_handler_ {ignore_ip4_up}; - upstream tcp_handler_ {ignore_ip4_up}; -}; //< class IP4 -} //< namespace net - -#endif diff --git a/api/net/ip4/arp.hpp b/api/net/ip4/arp.hpp new file mode 100644 index 0000000000..1a44fdbbe5 --- /dev/null +++ b/api/net/ip4/arp.hpp @@ -0,0 +1,154 @@ +// This file is a part of the IncludeOS unikernel - www.includeos.org +// +// Copyright 2015 Oslo and Akershus University College of Applied Sciences +// and Alfred Bratterud +// +// 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. + +#pragma once +#ifndef NET_IP4_ARP_HPP +#define NET_IP4_ARP_HPP + +#include +#include + +#include +#include "ip4.hpp" + +namespace net { + + class PacketArp; + + /** ARP manager, including an ARP-Cache. */ + class Arp { + private: + /** ARP cache expires after cache_exp_t_ seconds */ + static constexpr uint16_t cache_exp_t_ {60 * 60 * 12}; + + /** Cache entries are just MAC's and timestamps */ + struct cache_entry { + Ethernet::addr mac_; + uint64_t timestamp_; + + /** Map needs empty constructor (we have no emplace yet) */ + cache_entry() noexcept = default; + + cache_entry(Ethernet::addr mac) noexcept + : mac_(mac), timestamp_(OS::uptime()) {} + + cache_entry(const cache_entry& cpy) noexcept + : mac_(cpy.mac_), timestamp_(cpy.timestamp_) {} + + void update() noexcept { timestamp_ = OS::uptime(); } + }; //< struct cache_entry + + using Cache = std::map; + using PacketQueue = std::map; + public: + /** + * You can assign your own ARP-resolution delegate + * + * We're doing this to keep the HĂ„rek Haugerud mapping (HH_MAP) + */ + using Arp_resolver = delegate; + + enum Opcode { H_request = 0x100, H_reply = 0x200 }; + + /** Arp opcodes (Big-endian) */ + static constexpr uint16_t H_htype_eth {0x0100}; + static constexpr uint16_t H_ptype_ip4 {0x0008}; + static constexpr uint16_t H_hlen_plen {0x0406}; + + /** Constructor */ + explicit Arp(Inet&) noexcept; + + struct __attribute__((packed)) header { + Ethernet::header ethhdr; // Ethernet header + uint16_t htype; // Hardware type + uint16_t ptype; // Protocol type + uint16_t hlen_plen; // Protocol address length + uint16_t opcode; // Opcode + Ethernet::addr shwaddr; // Source mac + IP4::addr sipaddr; // Source ip + Ethernet::addr dhwaddr; // Target mac + IP4::addr dipaddr; // Target ip + }; + + /** Handle incoming ARP packet. */ + void bottom(Packet_ptr pckt); + + /** Roll your own arp-resolution system. */ + void set_resolver(Arp_resolver ar) + { arp_resolver_ = ar; } + + enum Resolver_name { DEFAULT, HH_MAP }; + + void set_resolver(Resolver_name nm) { + // @TODO: Add HÅREK-mapping here + switch (nm) { + case HH_MAP: + arp_resolver_ = Arp_resolver::from(*this); + break; + default: + arp_resolver_ = Arp_resolver::from(*this); + } + } + + /** Delegate link-layer output. */ + void set_linklayer_out(downstream link) + { linklayer_out_ = link; } + + /** Downstream transmission. */ + void transmit(Packet_ptr); + + private: + Inet& inet_; + + /** Needs to know which mac address to put in header->swhaddr */ + Ethernet::addr mac_; + + /** Outbound data goes through here */ + downstream linklayer_out_; + + /** The ARP cache */ + Cache cache_; + + /** Cache IP resolution. */ + void cache(IP4::addr, Ethernet::addr); + + /** Check if an IP is cached and not expired */ + bool is_valid_cached(IP4::addr); + + /** ARP resolution. */ + Ethernet::addr resolve(IP4::addr); + + void arp_respond(header* hdr_in); + + // two different ARP resolvers + void arp_resolve(Packet_ptr); + void hh_map(Packet_ptr); + + Arp_resolver arp_resolver_ = Arp_resolver::from(*this); + + PacketQueue waiting_packets_; + + /** Add a packet to waiting queue, to be sent when IP is resolved */ + void await_resolution(Packet_ptr, IP4::addr); + + /** Create a default initialized ARP-packet */ + Packet_ptr createPacket(); + }; //< class Arp + +} //< namespace net + +#endif //< NET_ARP_HPP diff --git a/api/net/ip4/icmpv4.hpp b/api/net/ip4/icmpv4.hpp index d0787fd783..c83ab74902 100644 --- a/api/net/ip4/icmpv4.hpp +++ b/api/net/ip4/icmpv4.hpp @@ -19,48 +19,48 @@ #define NET_IP4_ICMPv4_HPP #include "../inet.hpp" -#include "../ip4.hpp" +#include "ip4.hpp" namespace net { -void icmp_default_out(Packet_ptr); + void icmp_default_out(Packet_ptr); -class ICMPv4 { -public: - // Initialize - ICMPv4(Inet&); + class ICMPv4 { + public: + // Initialize + ICMPv4(Inet&); - // Known ICMP types - enum icmp_types { ICMP_ECHO_REPLY, ICMP_ECHO = 8 }; + // Known ICMP types + enum icmp_types { ICMP_ECHO_REPLY, ICMP_ECHO = 8 }; - struct icmp_header { - uint8_t type; - uint8_t code; - uint16_t checksum; - uint16_t identifier; - uint16_t sequence; - uint8_t payload[0]; - }__attribute__((packed)); + struct icmp_header { + uint8_t type; + uint8_t code; + uint16_t checksum; + uint16_t identifier; + uint16_t sequence; + uint8_t payload[0]; + }__attribute__((packed)); - struct full_header { - LinkLayer::header link_hdr; - IP4::ip_header ip_hdr; - icmp_header icmp_hdr; - }__attribute__((packed)); + struct full_header { + LinkLayer::header link_hdr; + IP4::ip_header ip_hdr; + icmp_header icmp_hdr; + }__attribute__((packed)); - // Input from network layer - void bottom(Packet_ptr); + // Input from network layer + void bottom(Packet_ptr); - // Delegate output to network layer - inline void set_network_out(downstream s) - { network_layer_out_ = s; }; + // Delegate output to network layer + inline void set_network_out(downstream s) + { network_layer_out_ = s; }; -private: - Inet& inet_; - downstream network_layer_out_ {icmp_default_out}; + private: + Inet& inet_; + downstream network_layer_out_ {icmp_default_out}; - void ping_reply(full_header* full_hdr, uint16_t size); -}; //< class ICMPv4 + void ping_reply(full_header* full_hdr, uint16_t size); + }; //< class ICMPv4 } //< namespace net #endif //< NET_IP4_ICMPv4_HPP diff --git a/api/net/ip4/ip4.hpp b/api/net/ip4/ip4.hpp new file mode 100644 index 0000000000..42dcd5d8a7 --- /dev/null +++ b/api/net/ip4/ip4.hpp @@ -0,0 +1,184 @@ +// This file is a part of the IncludeOS unikernel - www.includeos.org +// +// Copyright 2015 Oslo and Akershus University College of Applied Sciences +// and Alfred Bratterud +// +// 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. + +#ifndef CLASS_IP4_HPP +#define CLASS_IP4_HPP + +#include +#include + +#include +#include + +namespace net { + + // Default delegate assignments + void ignore_ip4_up(Packet_ptr); + void ignore_ip4_down(Packet_ptr); + + /** IP4 layer */ + class IP4 { + public: + /** Initialize. Sets a dummy linklayer out. */ + explicit IP4(Inet&) noexcept; + + /** Known transport layer protocols. */ + enum proto { IP4_ICMP=1, IP4_UDP=17, IP4_TCP=6 }; + + /** IP4 address representation */ + struct addr { + uint32_t whole; + + addr() : whole(0) {} // uninitialized + addr(uint32_t ipaddr) + : whole(ipaddr) {} + addr(uint8_t p1, uint8_t p2, uint8_t p3, uint8_t p4) + : whole(p1 | (p2 << 8) | (p3 << 16) | (p4 << 24)) {} + + inline addr& operator=(addr cpy) noexcept { + whole = cpy.whole; + return *this; + } + + /** Standard comparison operators */ + bool operator==(addr rhs) const noexcept + { return whole == rhs.whole; } + + bool operator==(const uint32_t rhs) const noexcept + { return whole == rhs; } + + bool operator<(const addr rhs) const noexcept + { return whole < rhs.whole; } + + bool operator<(const uint32_t rhs) const noexcept + { return whole < rhs; } + + bool operator>(const addr rhs) const noexcept + { return whole > rhs.whole; } + + bool operator>(const uint32_t rhs) const noexcept + { return whole > rhs; } + + bool operator!=(const addr rhs) const noexcept + { return whole != rhs.whole; } + + bool operator!=(const uint32_t rhs) const noexcept + { return whole != rhs; } + + addr operator & (addr rhs) const noexcept + { return addr(whole & rhs.whole); } + + /** x.x.x.x string representation */ + std::string str() const { + char ip_addr[16]; + sprintf(ip_addr, "%1i.%1i.%1i.%1i", + (whole >> 0) & 0xFF, + (whole >> 8) & 0xFF, + (whole >> 16) & 0xFF, + (whole >> 24) & 0xFF); + return ip_addr; + } + } __attribute__((packed)); //< IP4::addr + + static const addr INADDR_ANY; + static const addr INADDR_BCAST; + + /** IP4 header representation */ + struct ip_header { + uint8_t version_ihl; + uint8_t tos; + uint16_t tot_len; + uint16_t id; + uint16_t frag_off_flags; + uint8_t ttl; + uint8_t protocol; + uint16_t check; + addr saddr; + addr daddr; + }; + + /** + * The full header including IP + * + * @Note: This might be removed if we decide to isolate layers more + */ + struct full_header { + uint8_t link_hdr[sizeof(typename LinkLayer::header)]; + ip_header ip_hdr; + }; + + /* + Maximum Datagram Data Size + */ + inline constexpr uint16_t MDDS() const + { return stack_.MTU() - sizeof(ip_header); } + + /** Upstream: Input from link layer */ + void bottom(Packet_ptr); + + /** Upstream: Outputs to transport layer */ + inline void set_icmp_handler(upstream s) + { icmp_handler_ = s; } + + inline void set_udp_handler(upstream s) + { udp_handler_ = s; } + + inline void set_tcp_handler(upstream s) + { tcp_handler_ = s; } + + /** Downstream: Delegate linklayer out */ + void set_linklayer_out(downstream s) + { linklayer_out_ = s; }; + + /** + * Downstream: Receive data from above and transmit + * + * @note: The following *must be set* in the packet: + * + * * Destination IP + * * Protocol + * + * Source IP *can* be set - if it's not, IP4 will set it + */ + void transmit(Packet_ptr); + + /** Compute the IP4 header checksum */ + uint16_t checksum(ip_header*); + + /** + * \brief + * + * Returns the IPv4 address associated with this interface + **/ + const addr local_ip() const { + return stack_.ip_addr(); + } + + private: + Inet& stack_; + + /** Downstream: Linklayer output delegate */ + downstream linklayer_out_ {ignore_ip4_down}; + + /** Upstream delegates */ + upstream icmp_handler_ {ignore_ip4_up}; + upstream udp_handler_ {ignore_ip4_up}; + upstream tcp_handler_ {ignore_ip4_up}; + }; //< class IP4 +} //< namespace net + +#endif diff --git a/api/net/ip4/packet_arp.hpp b/api/net/ip4/packet_arp.hpp index ca1c82f39a..5091725e9d 100644 --- a/api/net/ip4/packet_arp.hpp +++ b/api/net/ip4/packet_arp.hpp @@ -19,7 +19,7 @@ #ifndef NET_IP4_PACKET_ARP #define NET_IP4_PACKET_ARP -#include "../arp.hpp" +#include "arp.hpp" #include namespace net diff --git a/api/net/ip4/packet_ip4.hpp b/api/net/ip4/packet_ip4.hpp index 7076fce840..d7a4d5e580 100644 --- a/api/net/ip4/packet_ip4.hpp +++ b/api/net/ip4/packet_ip4.hpp @@ -25,70 +25,71 @@ namespace net { -class PacketIP4 : public Packet, // might work as upcast: - public std::enable_shared_from_this -{ -public: - static constexpr size_t DEFAULT_TTL {64}; + class PacketIP4 : public Packet, // might work as upcast: + public std::enable_shared_from_this + { + public: + static constexpr size_t DEFAULT_TTL {64}; - const IP4::addr& src() const noexcept - { return ip4_header().saddr; } + const IP4::addr& src() const noexcept + { return ip4_header().saddr; } - void set_src(const IP4::addr& addr) noexcept - { ip4_header().saddr = addr; } + void set_src(const IP4::addr& addr) noexcept + { ip4_header().saddr = addr; } - const IP4::addr& dst() const noexcept - { return ip4_header().daddr; } + const IP4::addr& dst() const noexcept + { return ip4_header().daddr; } - void set_dst(const IP4::addr& addr) noexcept - { ip4_header().daddr = addr; } + void set_dst(const IP4::addr& addr) noexcept + { ip4_header().daddr = addr; } - void set_protocol(IP4::proto p) noexcept - { ip4_header().protocol = p; } + void set_protocol(IP4::proto p) noexcept + { ip4_header().protocol = p; } - uint8_t protocol() const noexcept - { return ip4_header().protocol; } + uint8_t protocol() const noexcept + { return ip4_header().protocol; } - uint16_t ip4_segment_size() const noexcept - { return ntohs(ip4_header().tot_len); } + uint16_t ip4_segment_size() const noexcept + { return ntohs(ip4_header().tot_len); } - /** Last modifications before transmission */ - void make_flight_ready() noexcept { - assert( ip4_header().protocol ); - set_segment_length(); - set_ip4_checksum(); - } + /** Last modifications before transmission */ + void make_flight_ready() noexcept { + assert( ip4_header().protocol ); + set_segment_length(); + set_ip4_checksum(); + } - void init() noexcept { - ip4_header().version_ihl = 0x45; - ip4_header().tos = 0; - ip4_header().id = 0; - ip4_header().frag_off_flags = 0; - ip4_header().ttl = DEFAULT_TTL; - } + void init() noexcept { + ip4_header().version_ihl = 0x45; + ip4_header().tos = 0; + ip4_header().id = 0; + ip4_header().frag_off_flags = 0; + ip4_header().ttl = DEFAULT_TTL; + } -private: - const IP4::ip_header& ip4_header() const noexcept - { return (reinterpret_cast(buffer()))->ip_hdr; } + private: + const IP4::ip_header& ip4_header() const noexcept + { return (reinterpret_cast(buffer()))->ip_hdr; } - IP4::ip_header& ip4_header() noexcept - { return (reinterpret_cast(buffer()))->ip_hdr; } + IP4::ip_header& ip4_header() noexcept + { return (reinterpret_cast(buffer()))->ip_hdr; } - /** - * Set IP4 header length - * - * Inferred from packet size and linklayer header size - */ - void set_segment_length() noexcept - { ip4_header().tot_len = htons(size() - sizeof(LinkLayer::header)); } + /** + * Set IP4 header length + * + * Inferred from packet size and linklayer header size + */ + void set_segment_length() noexcept + { ip4_header().tot_len = htons(size() - sizeof(LinkLayer::header)); } - void set_ip4_checksum() noexcept { - auto& hdr = ip4_header(); - hdr.check = 0; - hdr.check = net::checksum(&hdr, sizeof(IP4::ip_header)); - } - -}; //< class PacketIP4 + void set_ip4_checksum() noexcept { + auto& hdr = ip4_header(); + hdr.check = 0; + hdr.check = net::checksum(&hdr, sizeof(IP4::ip_header)); + } + + friend class IP4; + }; //< class PacketIP4 } //< namespace net #endif //< IP4_PACKET_IP4_HPP diff --git a/api/net/ip4/packet_tcp.hpp b/api/net/ip4/packet_tcp.hpp index a21fccf5f1..4507ed9627 100644 --- a/api/net/ip4/packet_tcp.hpp +++ b/api/net/ip4/packet_tcp.hpp @@ -26,7 +26,7 @@ namespace net /** A TCP Packet wrapper, with no data just methods. */ class TCP_packet : public PacketIP4, // might work as upcast: - public std::enable_shared_from_this + public std::enable_shared_from_this { public: diff --git a/api/net/ip4/udp.hpp b/api/net/ip4/udp.hpp index be016c3b87..cec1ec59a0 100644 --- a/api/net/ip4/udp.hpp +++ b/api/net/ip4/udp.hpp @@ -6,9 +6,9 @@ // 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. @@ -18,84 +18,132 @@ #ifndef NET_IP4_UDP_HPP #define NET_IP4_UDP_HPP +#include #include - #include "../inet.hpp" -#include "../ip4.hpp" +#include "ip4.hpp" +#include namespace net { -class PacketUDP; - -template -class Socket; - -void ignore_udp(Packet_ptr); - -/** Basic UDP support. @todo Implement UDP sockets. */ -class UDP { -public: - using addr_t = IP4::addr; - - /** UDP port number */ - using port_t = uint16_t; - - using Socket = Socket; - using Stack = Inet; - - /** UDP header */ - struct udp_header { - port_t sport; - port_t dport; - uint16_t length; - uint16_t checksum; - }; - - /** Full UDP Header with all sub-headers */ - struct full_header { - IP4::full_header full_hdr; - udp_header udp_hdr; - }__attribute__((packed)); - - //////////////////////////////////////////// - - inline addr_t local_ip() const - { return stack_.ip_addr(); } - - /** Input from network layer */ - void bottom(Packet_ptr); - - /** Delegate output to network layer */ - inline void set_network_out(downstream del) - { network_layer_out_ = del; } - - /** Send UDP datagram from source ip/port to destination ip/port. - - @param sip Local IP-address - @param sport Local port - @param dip Remote IP-address - @param dport Remote port */ - void transmit(std::shared_ptr udp); - - //! @param port local port - Socket& bind(port_t port); - - //! returns a new UDP socket bound to a random port - Socket& bind(); - - //! construct this UDP module with @inet - UDP(Stack& inet) : - network_layer_out_ {ignore_udp}, - stack_ {inet} - { } -private: - downstream network_layer_out_; - Stack& stack_; - std::map ports_; - port_t current_port_ {1024}; - - friend class SocketUDP; -}; //< class UDP + class PacketUDP; + class UDPSocket; + + /** Basic UDP support. @todo Implement UDP sockets. */ + class UDP { + public: + using addr_t = IP4::addr; + using port_t = uint16_t; + + using Packet_ptr = std::shared_ptr; + using Stack = Inet; + + typedef delegate sendto_handler; + + // write buffer for sendq + struct WriteBuffer + { + WriteBuffer( + const uint8_t* data, size_t length, sendto_handler cb, + UDP& udp, addr_t LA, port_t LP, addr_t DA, port_t DP); + + int remaining() const { + return len - offset; + } + bool done() const { + return offset == len; + } + + size_t packets_needed() const; + void write(); + + // buffer, total length and current write offset + std::shared_ptr buf; + size_t len; + size_t offset; + // the callback for when this buffer is written + sendto_handler callback; + // the UDP stack + UDP& udp; + + // port and addr this was being sent from + addr_t l_addr; + port_t l_port; + // destination address and port + port_t d_port; + addr_t d_addr; + }; + + /** UDP header */ + struct udp_header { + port_t sport; + port_t dport; + uint16_t length; + uint16_t checksum; + }; + + /** Full UDP Header with all sub-headers */ + struct full_header { + IP4::full_header full_hdr; + udp_header udp_hdr; + }__attribute__((packed)); + + //////////////////////////////////////////// + + addr_t local_ip() const + { return stack_.ip_addr(); } + + /** Input from network layer */ + void bottom(net::Packet_ptr); + + /** Delegate output to network layer */ + void set_network_out(downstream del) + { network_layer_out_ = del; } + + /** Send UDP datagram from source ip/port to destination ip/port. + + @param sip Local IP-address + @param sport Local port + @param dip Remote IP-address + @param dport Remote port */ + void transmit(UDP::Packet_ptr udp); + + //! @param port local port + UDPSocket& bind(port_t port); + + //! returns a new UDP socket bound to a random port + UDPSocket& bind(); + + //! construct this UDP module with @inet + UDP(Stack& inet); + + Stack& stack() + { + return stack_; + } + + // send as much as possible from sendq + void flush(); + + // create and transmit @num packets from sendq + void process_sendq(size_t num); + + inline constexpr uint16_t max_datagram_size() noexcept { + return stack().ip_obj().MDDS() - sizeof(udp_header); + } + + private: + + downstream network_layer_out_; + Stack& stack_; + std::map ports_; + port_t current_port_ {1024}; + + // the async send queue + std::deque sendq; + friend class net::UDPSocket; + }; //< class UDP + } //< namespace net #include "packet_udp.hpp" diff --git a/api/net/ip4/udp_socket.hpp b/api/net/ip4/udp_socket.hpp index 7ca4ff7d26..ea5ea219ff 100644 --- a/api/net/ip4/udp_socket.hpp +++ b/api/net/ip4/udp_socket.hpp @@ -6,15 +6,16 @@ // 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. +#pragma once #ifndef NET_IP4_UDP_SOCKET_HPP #define NET_IP4_UDP_SOCKET_HPP #include "udp.hpp" @@ -22,71 +23,64 @@ namespace net { - template - class Socket; - - template<> - class Socket + class UDPSocket { public: - typedef UDP::port_t port; - typedef IP4::addr addr; + typedef UDP::port_t port_t; + typedef IP4::addr addr_t; typedef IP4::addr multicast_group_addr; - - typedef delegate&, addr, port, const char*, int)> recvfrom_handler; - typedef delegate&, addr, port, const char*, int)> sendto_handler; - + + typedef delegate recvfrom_handler; + typedef UDP::sendto_handler sendto_handler; + // constructors - Socket(Inet&, port port); - Socket(const Socket&) = delete; + UDPSocket(UDP&, port_t port); + UDPSocket(const UDPSocket&) = delete; // ^ DON'T USE THESE. We could create our own allocators just to prevent // you from creating sockets, but then everyone is wasting time. // These are public to allow us to use emplace(...). // Use Stack.udp().bind(port) to get a valid Socket reference. - + // functions - inline void onRead(recvfrom_handler func) - { - on_read = func; - } - inline void onWrite(sendto_handler func) + void on_read(recvfrom_handler callback) { - on_send = func; + on_read_handler = callback; } - int sendto(addr destIP, port port, - const void* buffer, int length); - int bcast(addr srcIP, port port, - const void* buffer, int length); + void sendto(addr_t destIP, port_t port, + const void* buffer, size_t length, + sendto_handler cb = [] {}); + void bcast(addr_t srcIP, port_t port, + const void* buffer, size_t length, + sendto_handler cb = [] {}); void close(); - - void join(multicast_group_addr&); - void leave(multicast_group_addr&); - + + void join(multicast_group_addr); + void leave(multicast_group_addr); + // stuff - addr local_addr() const + addr_t local_addr() const { - return stack.ip_addr(); + return udp.local_ip(); } - port local_port() const + port_t local_port() const { return l_port; } - + private: - void packet_init(std::shared_ptr, addr, addr, port, uint16_t); - int internal_read(std::shared_ptr); - int internal_write(addr, addr, port, const uint8_t*, int); - - Inet& stack; - port l_port; - recvfrom_handler on_read = [](Socket&, addr, port, const char*, int)->int{ return 0; }; - sendto_handler on_send = [](Socket&, addr, port, const char*, int)->int{ return 0; }; - + void packet_init(UDP::Packet_ptr, addr_t, addr_t, port_t, uint16_t); + void internal_read(UDP::Packet_ptr); + + UDP& udp; + port_t l_port; + recvfrom_handler on_read_handler = + [] (addr_t, port_t, const char*, size_t) {}; + bool reuse_addr; bool loopback; // true means multicast data is looped back to sender - + friend class UDP; - friend class std::allocator; + friend class std::allocator; }; } diff --git a/api/net/ip6/icmp6.hpp b/api/net/ip6/icmp6.hpp index eb4d48d0e4..b26d28e5c5 100644 --- a/api/net/ip6/icmp6.hpp +++ b/api/net/ip6/icmp6.hpp @@ -144,6 +144,6 @@ namespace net size_ = sizeof(IP6::full_header) + icmp_len; } - }; + }; } diff --git a/api/net/ip6/ip6.hpp b/api/net/ip6/ip6.hpp index 31db6d09f0..031ddd2b35 100644 --- a/api/net/ip6/ip6.hpp +++ b/api/net/ip6/ip6.hpp @@ -43,17 +43,17 @@ namespace net public: /** Known transport layer protocols. */ enum proto - { - PROTO_HOPOPT = 0, // IPv6 hop-by-hop + { + PROTO_HOPOPT = 0, // IPv6 hop-by-hop - PROTO_ICMPv4 = 1, - PROTO_TCP = 6, - PROTO_UDP = 17, + PROTO_ICMPv4 = 1, + PROTO_TCP = 6, + PROTO_UDP = 17, - PROTO_ICMPv6 = 58, // IPv6 ICMP - PROTO_NoNext = 59, // no next-header - PROTO_OPTSv6 = 60, // dest options - }; + PROTO_ICMPv6 = 58, // IPv6 ICMP + PROTO_NoNext = 59, // no next-header + PROTO_OPTSv6 = 60, // dest options + }; struct addr { @@ -63,23 +63,32 @@ namespace net addr(uint16_t a1, uint16_t a2, uint16_t b1, uint16_t b2, uint16_t c1, uint16_t c2, uint16_t d1, uint16_t d2) { - i128 = _mm_set_epi16( - //d2, d1, c2, c1, b2, b1, a2, a1); - htons(d2), htons(d1), - htons(c2), htons(c1), - htons(b2), htons(b1), - htons(a2), htons(a1)); + i16[0] = a1; i16[1] = a2; + i16[2] = b1; i16[3] = b2; + i16[4] = c1; i16[5] = c2; + i16[6] = d1; i16[7] = d2; + /*i128 = _mm_set_epi16( + htons(d2), htons(d1), + htons(c2), htons(c1), + htons(b2), htons(b1), + htons(a2), htons(a1));*/ } addr(uint32_t a, uint32_t b, uint32_t c, uint32_t d) { - i128 = _mm_set_epi32(d, c, b, a); + i32[0] = a; i32[1] = b; i32[2] = c; i32[3] = d; + //i128 = _mm_set_epi32(d, c, b, a); } addr(const addr& a) - : i128(a.i128) {} + { + for (int i = 0; i < 4; i++) + i32[i] = a.i32[i]; + } // move constructor addr& operator= (const addr& a) { - i128 = a.i128; + for (int i = 0; i < 4; i++) + i32[i] = a.i32[i]; + //i128 = a.i128; return *this; } @@ -87,8 +96,11 @@ namespace net bool operator== (const addr& a) const { // i128 == a.i128: - __m128i cmp = _mm_cmpeq_epi32(i128, a.i128); - return _mm_cvtsi128_si32(cmp); + for (int i = 0; i < 4; i++) + if (i32[i] != a.i32[i]) return false; + return true; + //__m128i cmp = _mm_cmpeq_epi32(i128, a.i128); + //return _mm_cvtsi128_si32(cmp); } bool operator!= (const addr& a) const { @@ -121,28 +133,29 @@ namespace net bool is_multicast() const { /** - RFC 4291 2.7 Multicast Addresses + RFC 4291 2.7 Multicast Addresses - An IPv6 multicast address is an identifier for a group of interfaces - (typically on different nodes). An interface may belong to any - number of multicast groups. Multicast addresses have the following format: - | 8 | 4 | 4 | 112 bits | - +------ -+----+----+---------------------------------------------+ - |11111111|flgs|scop| group ID | - +--------+----+----+---------------------------------------------+ + An IPv6 multicast address is an identifier for a group of interfaces + (typically on different nodes). An interface may belong to any + number of multicast groups. Multicast addresses have the following format: + | 8 | 4 | 4 | 112 bits | + +------ -+----+----+---------------------------------------------+ + |11111111|flgs|scop| group ID | + +--------+----+----+---------------------------------------------+ **/ return i8[0] == 0xFF; } union { - __m128i i128; + //__m128i i128; uint32_t i32[ 4]; + uint16_t i16[ 8]; uint8_t i8[16]; }; - } __attribute__((aligned(alignof(__m128i)))); + }; - #pragma pack(push, 1) +#pragma pack(push, 1) class header { public: @@ -153,7 +166,7 @@ namespace net uint8_t tclass() const { return ((scanline[0] & 0xF000) >> 12) + - (scanline[0] & 0xF); + (scanline[0] & 0xF); } // initializes the first scanline with the IPv6 version void init_scan0() @@ -164,7 +177,7 @@ namespace net uint16_t size() const { return ((scanline[1] & 0x00FF) << 8) + - ((scanline[1] & 0xFF00) >> 8); + ((scanline[1] & 0xFF00) >> 8); } void set_size(uint16_t newsize) { @@ -218,7 +231,7 @@ namespace net return hdr_ext_len; } }; - #pragma pack(pop) +#pragma pack(pop) struct full_header { @@ -243,25 +256,25 @@ namespace net static std::string protocol_name(uint8_t protocol) { switch (protocol) - { - case PROTO_HOPOPT: - return "IPv6 Hop-By-Hop (0)"; + { + case PROTO_HOPOPT: + return "IPv6 Hop-By-Hop (0)"; - case PROTO_TCP: - return "TCPv6 (6)"; - case PROTO_UDP: - return "UDPv6 (17)"; + case PROTO_TCP: + return "TCPv6 (6)"; + case PROTO_UDP: + return "UDPv6 (17)"; - case PROTO_ICMPv6: - return "ICMPv6 (58)"; - case PROTO_NoNext: - return "No next header (59)"; - case PROTO_OPTSv6: - return "IPv6 destination options (60)"; + case PROTO_ICMPv6: + return "ICMPv6 (58)"; + case PROTO_NoNext: + return "No next header (59)"; + case PROTO_OPTSv6: + return "IPv6 destination options (60)"; - default: - return "Unknown: " + std::to_string(protocol); - } + default: + return "Unknown: " + std::to_string(protocol); + } } // handler for upstream IPv6 packets @@ -283,7 +296,7 @@ namespace net // creates a new IPv6 packet to be sent over the ether static std::shared_ptr create(uint8_t proto, - Ethernet::addr ether_dest, const IP6::addr& dest); + Ethernet::addr ether_dest, const IP6::addr& dest); private: addr local; diff --git a/api/net/ip6/tcp6.hpp b/api/net/ip6/tcp6.hpp index 013ea32c32..96cd732076 100644 --- a/api/net/ip6/tcp6.hpp +++ b/api/net/ip6/tcp6.hpp @@ -6,12 +6,11 @@ // 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. - diff --git a/api/net/ip6/udp6.hpp b/api/net/ip6/udp6.hpp index 66ac4ec510..380128363d 100644 --- a/api/net/ip6/udp6.hpp +++ b/api/net/ip6/udp6.hpp @@ -72,7 +72,7 @@ namespace net // creates a new packet to be sent over the ether std::shared_ptr create( - Ethernet::addr ether_dest, const IP6::addr& dest, port_t port); + Ethernet::addr ether_dest, const IP6::addr& dest, port_t port); private: std::map listeners; @@ -125,7 +125,7 @@ namespace net header().length = htons(sizeof(UDPv6::header) + newlen); // new total IPv6 payload length ip6_header().set_size( - sizeof(IP6::header) + sizeof(UDPv6::header) + newlen); + sizeof(IP6::header) + sizeof(UDPv6::header) + newlen); // new total packet length size_ = sizeof(IP6::full_header) + sizeof(UDPv6::header) + newlen; } diff --git a/api/net/packet.hpp b/api/net/packet.hpp index 22752ea784..c8244655a4 100644 --- a/api/net/packet.hpp +++ b/api/net/packet.hpp @@ -6,9 +6,9 @@ // 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. @@ -18,133 +18,138 @@ #ifndef NET_PACKET_HPP #define NET_PACKET_HPP -#include -#include +#include "buffer_store.hpp" +#include "ip4/ip4.hpp" +#include namespace net { -/** Default buffer release-function. Returns the buffer to Packet's bufferStore **/ -void default_release(BufferStore::buffer_t, size_t); - -class Packet : public std::enable_shared_from_this { -public: - static constexpr size_t MTU {1500}; - - using release_del = BufferStore::release_del; - - /** - * Construct, using existing buffer. - * - * @param buf: The buffer to use as the packet - * @param bufsize: Size of the buffer - * @param datalen: Length of data in the buffer - * - * @WARNING: There are two adjacent parameters of the same type, violating CG I.24. - */ - Packet(BufferStore::buffer_t buf, size_t bufsize, size_t datalen, release_del d = default_release) noexcept; - - /** Destruct. */ - virtual ~Packet(); - - /** Get the buffer */ - BufferStore::buffer_t buffer() const noexcept - { return buf_; } - - /** Get the network packet length - i.e. the number of populated bytes */ - inline uint32_t size() const noexcept - { return size_; } - - /** Get the size of the buffer. This is >= len(), usually MTU-size */ - inline uint32_t capacity() const noexcept - { return capacity_; } - - int set_size(const size_t) noexcept; - - /** Set next-hop ip4. */ - void next_hop(IP4::addr ip) noexcept; - - /** Get next-hop ip4. */ - IP4::addr next_hop() const noexcept; - - /** - * Add a packet to this packet chain. - * - * @Warning: Let's hope this recursion won't smash the stack - */ - void chain(Packet_ptr p) noexcept { - if (!chain_) chain_ = p; - else chain_->chain(p); - } - - /** - * Get the next packet in the chain. - * - * @Todo: Make chain iterators (i.e. begin / end) */ - Packet_ptr unchain() noexcept - { return chain_; } - - /** Get the the total number of packets in the chain */ - size_t chain_length() const noexcept { - if (!chain_) { - return 1; + /** Default buffer release-function. Returns the buffer to Packet's bufferStore **/ + void default_release(BufferStore::buffer_t, size_t); + + class Packet : public std::enable_shared_from_this { + public: + using release_del = BufferStore::release_del; + + /** + * Construct, using existing buffer. + * + * @param buf: The buffer to use as the packet + * @param bufsize: Size of the buffer + * @param datalen: Length of data in the buffer + * + * @WARNING: There are two adjacent parameters of the same type, violating CG I.24. + */ + Packet(BufferStore::buffer_t buf, size_t bufsize, size_t datalen, release_del d = default_release) noexcept; + + /** Destruct. */ + virtual ~Packet(); + + /** Get the buffer */ + BufferStore::buffer_t buffer() const noexcept + { return buf_; } + + /** Get the network packet length - i.e. the number of populated bytes */ + inline uint32_t size() const noexcept + { return size_; } + + /** Get the size of the buffer. This is >= len(), usually MTU-size */ + inline uint32_t capacity() const noexcept + { return capacity_; } + + int set_size(const size_t) noexcept; + + /** Set next-hop ip4. */ + void next_hop(IP4::addr ip) noexcept; + + /** Get next-hop ip4. */ + IP4::addr next_hop() const noexcept; + + /* Add a packet to this packet chain. */ + void chain(Packet_ptr p) noexcept { + if (!chain_) { + chain_ = p; + last_ = p; + } else { + last_->chain(p); + last_ = p->last_in_chain() ? p->last_in_chain() : p; + assert(last_); + } + } + + /* Get the last packet in the chain */ + Packet_ptr last_in_chain() noexcept + { return last_; } + + /* Get the tail, i.e. chain minus the first element */ + Packet_ptr tail() noexcept + { return chain_; } + + /* Get the tail, and detach it from the head (for FIFO) */ + Packet_ptr detach_tail() noexcept + { + auto tail = chain_; + chain_ = 0; + return tail; } - return 1 + chain_->chain_length(); - } - - /** - * For a UDPv6 packet, the payload location is the start of - * the UDPv6 header, and so on - */ - inline void set_payload(BufferStore::buffer_t location) noexcept - { payload_ = location; } - - /** Get the payload of the packet */ - inline BufferStore::buffer_t payload() const noexcept - { return payload_; } - - /** - * Upcast back to normal packet - * - * Unfortunately, we can't upcast with std::static_pointer_cast - * however, all classes derived from Packet should be good to use - */ - static Packet_ptr packet(Packet_ptr pckt) noexcept - { return *static_cast(&pckt); } - - /** @Todo: Avoid Protected Data. (Jedi Council CG, C.133) **/ -protected: - BufferStore::buffer_t payload_ {nullptr}; - BufferStore::buffer_t buf_ {nullptr}; - size_t capacity_ {MTU}; // NOTE: Actual value is provided by BufferStore - size_t size_ {0}; - IP4::addr next_hop4_ {}; -private: - /** Send the buffer back home, after destruction */ - release_del release_; - - /** Let's chain packets */ - Packet_ptr chain_ {}; - - /** Default constructor Deleted. See Packet(Packet&). */ - Packet() = delete; - - /** - * Delete copy and move because we want Packets and buffers to be 1 to 1 - * - * (Well, we really deleted this to avoid accidental copying) - * - * The idea is to use Packet_ptr (i.e. shared_ptr) for passing packets. - * - * @todo Add an explicit way to copy packets. - */ - Packet(Packet&) = delete; - Packet(Packet&&) = delete; - - /** Delete copy and move assignment operators. See Packet(Packet&). */ - Packet& operator=(Packet) = delete; - Packet operator=(Packet&&) = delete; -}; //< class Packet + + /** + * For a UDPv6 packet, the payload location is the start of + * the UDPv6 header, and so on + */ + inline void set_payload(BufferStore::buffer_t location) noexcept + { payload_ = location; } + + /** Get the payload of the packet */ + inline BufferStore::buffer_t payload() const noexcept + { return payload_; } + + /** + * Upcast back to normal packet + * + * Unfortunately, we can't upcast with std::static_pointer_cast + * however, all classes derived from Packet should be good to use + */ + static Packet_ptr packet(Packet_ptr pckt) noexcept + { return *static_cast(&pckt); } + + /** @Todo: Avoid Protected Data. (Jedi Council CG, C.133) **/ + protected: + BufferStore::buffer_t payload_ {nullptr}; + BufferStore::buffer_t buf_ {nullptr}; + size_t capacity_ {0}; // NOTE: Actual value is provided by BufferStore + size_t size_ {0}; + IP4::addr next_hop4_ {}; + private: + /** Send the buffer back home, after destruction */ + release_del release_; + + /** Let's chain packets */ + Packet_ptr chain_ {0}; + Packet_ptr last_ {0}; + + /** Default constructor Deleted. See Packet(Packet&). */ + Packet() = delete; + + /** + * Delete copy and move because we want Packets and buffers to be 1 to 1 + * + * (Well, we really deleted this to avoid accidental copying) + * + * The idea is to use Packet_ptr (i.e. shared_ptr) for passing packets. + * + * @todo Add an explicit way to copy packets. + */ + Packet(Packet&) = delete; + Packet(Packet&&) = delete; + + + /** Delete copy and move assignment operators. See Packet(Packet&). */ + Packet& operator=(Packet) = delete; + Packet operator=(Packet&&) = delete; + }; //< class Packet + } //< namespace net #endif diff --git a/api/net/tcp.hpp b/api/net/tcp.hpp index dbf9bfdbe8..12c8686ff2 100644 --- a/api/net/tcp.hpp +++ b/api/net/tcp.hpp @@ -6,9 +6,9 @@ // 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. @@ -19,1057 +19,1807 @@ #define NET_TCP_HPP #include -#include // IP4::Addr -#include // PacketIP4 -#include // net::Packet_ptr, htons / noths +#include "ip4/ip4.hpp" // IP4::Addr +#include "ip4/packet_ip4.hpp" // PacketIP4 +#include "util.hpp" // net::Packet_ptr, htons / noths #include // buffer #include #include // ostringstream #include // timer duration #include // enable_shared_from_this +#include + +inline unsigned round_up(unsigned n, unsigned div) { + assert(n); + return (n + div - 1) / div; +} namespace net { -class TCP { -public: - using Address = IP4::addr; - using Port = uint16_t; - /* - A Sequence number (SYN/ACK) (32 bits) - */ - using Seq = uint32_t; - class Packet; - using Packet_ptr = std::shared_ptr; - class Connection; - using Connection_ptr = std::shared_ptr; - using IPStack = Inet; - -public: - /* - An IP address and a Port. - */ - class Socket { - public: - /* - Intialize an empty socket. - */ - inline Socket() : address_(), port_(0) { address_.whole = 0; }; - - /* - Create a socket with a Address and Port. - */ - inline Socket(Address address, Port port) : address_(address), port_(port) {}; - - /* - Returns the Socket's address. - */ - inline const TCP::Address address() const { return address_; } - - /* - Returns the Socket's port. - */ - inline TCP::Port port() const { return port_; } - - /* - Returns a string in the format "Address:Port". - */ - std::string to_string() const { - std::stringstream ss; - ss << address_.str() << ":" << port_; - return ss.str(); - } - - inline bool is_empty() const { return (address_.whole == 0 and port_ == 0); } - - /* - Comparator used for vector. - */ - inline bool operator ==(const Socket &s2) const { - return address().whole == s2.address().whole - and port() == s2.port(); - } - - /* - Comparator used for map. - */ - inline bool operator <(const Socket& s2) const { - return address().whole < s2.address().whole - or (address().whole == s2.address().whole and port() < s2.port()); - } - - private: - //SocketID id_; // Maybe a hash or something. Not sure if needed (yet) - TCP::Address address_; - TCP::Port port_; - - }; // << class TCP::Socket - - - /////// TCP Stuff - Relevant to the protocol ///// - - static constexpr uint16_t default_window_size = 0xffff; - - /* - Flags (Control bits) in the TCP Header. - */ - enum Flag { - NS = (1 << 8), // Nounce (Experimental: see RFC 3540) - CWR = (1 << 7), // Congestion Window Reduced - ECE = (1 << 6), // ECN-Echo - URG = (1 << 5), // Urgent - ACK = (1 << 4), // Acknowledgement - PSH = (1 << 3), // Push - RST = (1 << 2), // Reset - SYN = (1 << 1), // Syn(chronize) - FIN = 1, // Fin(ish) + class TCP { + public: + using Address = IP4::addr; + using Port = uint16_t; + /* + A Sequence number (SYN/ACK) (32 bits) + */ + using Seq = uint32_t; + + using buffer_t = std::shared_ptr; + + class Packet; + using Packet_ptr = std::shared_ptr; + + class TCPException; + class TCPBadOptionException; + + class Connection; + using Connection_ptr = std::shared_ptr; + using IPStack = Inet; + + public: + /* + An IP address and a Port. + */ + class Socket { + public: + /* + Intialize an empty socket. + */ + inline Socket() : address_(), port_(0) { address_.whole = 0; }; + + /* + Create a socket with a Address and Port. + */ + inline Socket(Address address, Port port) : address_(address), port_(port) {}; + + /* + Returns the Socket's address. + */ + inline const TCP::Address address() const { return address_; } + + /* + Returns the Socket's port. + */ + inline TCP::Port port() const { return port_; } + + /* + Returns a string in the format "Address:Port". + */ + std::string to_string() const { + std::stringstream ss; + ss << address_.str() << ":" << port_; + return ss.str(); + } + + inline bool is_empty() const { return (address_.whole == 0 and port_ == 0); } + + /* + Comparator used for vector. + */ + inline bool operator ==(const Socket &s2) const { + return address().whole == s2.address().whole + and port() == s2.port(); + } + + /* + Comparator used for map. + */ + inline bool operator <(const Socket& s2) const { + return address().whole < s2.address().whole + or (address().whole == s2.address().whole and port() < s2.port()); + } + + private: + //SocketID id_; // Maybe a hash or something. Not sure if needed (yet) + TCP::Address address_; + TCP::Port port_; + + }; // << class TCP::Socket + + + /////// TCP Stuff - Relevant to the protocol ///// + + static constexpr uint16_t default_window_size = 0xffff; + + static constexpr uint16_t default_mss = 536; + + /* + Flags (Control bits) in the TCP Header. + */ + enum Flag { + NS = (1 << 8), // Nounce (Experimental: see RFC 3540) + CWR = (1 << 7), // Congestion Window Reduced + ECE = (1 << 6), // ECN-Echo + URG = (1 << 5), // Urgent + ACK = (1 << 4), // Acknowledgement + PSH = (1 << 3), // Push + RST = (1 << 2), // Reset + SYN = (1 << 1), // Syn(chronize) + FIN = 1, // Fin(ish) + }; + + /* + Representation of the TCP Header. + + RFC 793, (p.15): + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Source Port | Destination Port | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Sequence Number | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Acknowledgment Number | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Data | |U|A|P|R|S|F| | + | Offset| Reserved |R|C|S|S|Y|I| Window | + | | |G|K|H|T|N|N| | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Checksum | Urgent Pointer | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Options | Padding | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | data | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ + struct Header { + TCP::Port source_port; // Source port + TCP::Port destination_port; // Destination port + uint32_t seq_nr; // Sequence number + uint32_t ack_nr; // Acknowledge number + union { + uint16_t whole; // Reference to offset_reserved & flags together. + struct { + uint8_t offset_reserved; // Offset (4 bits) + Reserved (3 bits) + NS (1 bit) + uint8_t flags; // All Flags (Control bits) except NS (9 bits - 1 bit) + }; + } offset_flags; // Data offset + Reserved + Flags (16 bits) + uint16_t window_size; // Window size + uint16_t checksum; // Checksum + uint16_t urgent; // Urgent pointer offset + uint8_t options[0]; // Options + }__attribute__((packed)); // << struct TCP::Header + + + /* + TCP Pseudo header, for checksum calculation + */ + struct Pseudo_header { + IP4::addr saddr; + IP4::addr daddr; + uint8_t zero; + uint8_t proto; + uint16_t tcp_length; + }__attribute__((packed)); + + /* + TCP Checksum-header (TCP-header + pseudo-header) + */ + struct Checksum_header { + TCP::Pseudo_header pseudo; + TCP::Header tcp; + }__attribute__((packed)); + + /* + To extract the TCP part from a Packet_ptr and calculate size. (?) + */ + struct Full_header { + Ethernet::header ethernet; + IP4::ip_header ip4; + TCP::Header tcp; + }__attribute__((packed)); + + /* + TCP Header Option + */ + struct Option { + uint8_t kind; + uint8_t length; + uint8_t data[0]; + + enum Kind { + END = 0x00, // End of option list + NOP = 0x01, // No-Opeartion + MSS = 0x02, // Maximum Segment Size [RFC 793] Rev: [879, 6691] + }; + + static std::string kind_string(Kind kind) { + switch(kind) { + case MSS: + return {"MSS"}; + + default: + return {"Unknown Option"}; + } + } + + struct opt_mss { + uint8_t kind; + uint8_t length; + uint16_t mss; + + opt_mss(uint16_t mss) + : kind(MSS), length(4), mss(htons(mss)) {} + }; + + struct opt_timestamp { + uint8_t kind; + uint8_t length; + uint32_t ts_val; + uint32_t ts_ecr; + }; + }; + + + /* + A Wrapper for a TCP Packet. Is everything as a IP4 Packet, + in addition to the TCP Header and functions to modify this and the control bits (FLAGS). + */ + class Packet : public PacketIP4 { + public: + + inline TCP::Header& header() const + { + return ((TCP::Full_header*) buffer())->tcp; + } + + static const size_t HEADERS_SIZE = sizeof(TCP::Full_header); + + //! initializes to a default, empty TCP packet, given + //! a valid MTU-sized buffer + void init() + { + // Erase all headers (smart? necessary? ...well, convenient) + memset(buffer(), 0, HEADERS_SIZE); + PacketIP4::init(); + + set_protocol(IP4::IP4_TCP); + set_win(TCP::default_window_size); + set_offset(5); + + // set TCP payload location (!?) + set_payload(buffer() + all_headers_len()); + } + + // GETTERS + inline TCP::Port src_port() const { return ntohs(header().source_port); } + + inline TCP::Port dst_port() const { return ntohs(header().destination_port); } + + inline TCP::Seq seq() const { return ntohl(header().seq_nr); } + + inline TCP::Seq ack() const { return ntohl(header().ack_nr); } + + inline uint16_t win() const { return ntohs(header().window_size); } + + inline TCP::Socket source() const { return TCP::Socket{src(), src_port()}; } + + inline TCP::Socket destination() const { return TCP::Socket{dst(), dst_port()}; } + + inline TCP::Seq end() const { return seq() + data_length(); } + + // SETTERS + inline TCP::Packet& set_src_port(TCP::Port p) { + header().source_port = htons(p); + return *this; + } + + inline TCP::Packet& set_dst_port(TCP::Port p) { + header().destination_port = htons(p); + return *this; + } + + inline TCP::Packet& set_seq(TCP::Seq n) { + header().seq_nr = htonl(n); + return *this; + } + + inline TCP::Packet& set_ack(TCP::Seq n) { + header().ack_nr = htonl(n); + return *this; + } + + inline TCP::Packet& set_win(uint16_t size) { + header().window_size = htons(size); + return *this; + } + + inline TCP::Packet& set_checksum(uint16_t checksum) { + header().checksum = checksum; + return *this; + } + + inline TCP::Packet& set_source(const TCP::Socket& src) { + set_src(src.address()); // PacketIP4::set_src + set_src_port(src.port()); + return *this; + } + + inline TCP::Packet& set_destination(const TCP::Socket& dest) { + set_dst(dest.address()); // PacketIP4::set_dst + set_dst_port(dest.port()); + return *this; + } + + /// FLAGS / CONTROL BITS /// + + inline TCP::Packet& set_flag(TCP::Flag f) { + header().offset_flags.whole |= htons(f); + return *this; + } + + inline TCP::Packet& set_flags(uint16_t f) { + header().offset_flags.whole |= htons(f); + return *this; + } + + inline TCP::Packet& clear_flag(TCP::Flag f) { + header().offset_flags.whole &= ~ htons(f); + return *this; + } + + inline TCP::Packet& clear_flags() { + header().offset_flags.whole &= 0x00ff; + return *this; + } + + inline bool isset(TCP::Flag f) { return ntohs(header().offset_flags.whole) & f; } + + //TCP::Flag flags() const { return (htons(header().offset_flags.whole) << 8) & 0xFF; } + + + /// OFFSET, OPTIONS, DATA /// + + // Get the raw tcp offset, in quadruples + inline uint8_t offset() const { return (uint8_t)(header().offset_flags.offset_reserved >> 4); } + + // Set raw TCP offset in quadruples + inline void set_offset(uint8_t offset) { header().offset_flags.offset_reserved = (offset << 4); } + + // The actaul TCP header size (including options). + inline uint8_t header_size() const { return offset() * 4; } + + // Calculate the full header length, down to linklayer, in bytes + uint8_t all_headers_len() const { return (HEADERS_SIZE - sizeof(TCP::Header)) + header_size(); } + + // Where data starts + inline char* data() { return (char*) (buffer() + all_headers_len()); } + + inline uint16_t data_length() const { return size() - all_headers_len(); } + + inline bool has_data() const { return data_length() > 0; } + + inline uint16_t size_available() const + { return size() - all_headers_len() - data_length(); } + + inline uint16_t tcp_length() const { return header_size() + data_length(); } + + template + inline void add_option(Args&&... args) { + // to avoid headache, options need to be added BEFORE any data. + assert(!has_data()); + // option address + auto* addr = options()+options_length(); + new (addr) T(args...); + // update offset + set_offset(offset() + round_up( ((T*)addr)->length, 4 )); + set_length(); // update + } + + inline void clear_options() { + // clear existing options + // move data (if any) (??) + set_offset(5); + set_length(); // update + } + + inline uint8_t* options() { return (uint8_t*) header().options; } + + inline uint8_t options_length() const { return header_size() - sizeof(TCP::Header); } + + inline bool has_options() const { return options_length() > 0; } + + // sets the correct length for all the protocols up to IP4 + void set_length(uint16_t newlen = 0) { + // new total packet length + set_size( all_headers_len() + newlen ); + } + + //! assuming the packet has been properly initialized, + //! this will fill bytes from @buffer into this packets buffer, + //! then return the number of bytes written. buffer is unmodified + size_t fill(const char* buffer, size_t length) { + size_t rem = capacity() - all_headers_len(); + size_t total = (length < rem) ? length : rem; + // copy from buffer to packet buffer + memcpy(data() + data_length(), buffer, total); + // set new packet length + set_length(data_length() + total); + return total; + } + + bool is_acked_by(const Seq ack) const { + return ack >= (seq() + data_length()); + } + + inline std::string to_string() { + std::ostringstream os; + os << "[ S:" << source().to_string() << " D:" << destination().to_string() + << " SEQ:" << seq() << " ACK:" << ack() + << " HEAD-LEN:" << (int)header_size() << " OPT-LEN:" << (int)options_length() << " DATA-LEN:" << data_length() + << " WIN:" << win() << " FLAGS:" << std::bitset<8>{header().offset_flags.flags} << " ]"; + return os.str(); + } + + }; // << class TCP::Packet + + /* + TODO: Does this need to be better? (faster? stronger?) + */ + class TCPException : public std::runtime_error { + public: + TCPException(const std::string& error) : std::runtime_error(error) {}; + virtual ~TCPException() {}; + }; + + /* + Exception for Bad TCP Header Option (TCP::Option) + */ + class TCPBadOptionException : public TCPException { + public: + TCPBadOptionException(Option::Kind kind, const std::string& error) : + TCPException("Bad Option [" + Option::kind_string(kind) + "]: " + error), + kind_(kind) {}; + + Option::Kind kind(); + private: + Option::Kind kind_; }; /* - Representation of the TCP Header. - - RFC 793, (p.15): - 0 1 2 3 - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Source Port | Destination Port | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Sequence Number | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Acknowledgment Number | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Data | |U|A|P|R|S|F| | - | Offset| Reserved |R|C|S|S|Y|I| Window | - | | |G|K|H|T|N|N| | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Checksum | Urgent Pointer | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Options | Padding | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | data | - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + A connection between two Sockets (local and remote). + Receives and handle TCP::Packet. + Transist between many states. */ - struct Header { - TCP::Port source_port; // Source port - TCP::Port destination_port; // Destination port - uint32_t seq_nr; // Sequence number - uint32_t ack_nr; // Acknowledge number - union { - uint16_t whole; // Reference to offset_reserved & flags together. - struct { - uint8_t offset_reserved; // Offset (4 bits) + Reserved (3 bits) + NS (1 bit) - uint8_t flags; // All Flags (Control bits) except NS (9 bits - 1 bit) - }; - } offset_flags; // Data offset + Reserved + Flags (16 bits) - uint16_t window_size; // Window size - uint16_t checksum; // Checksum - uint16_t urgent; // Urgent pointer offset - uint32_t options[0]; // Options - }__attribute__((packed)); // << struct TCP::Header - - - /* - TCP Pseudo header, for checksum calculation - */ - struct Pseudo_header { - IP4::addr saddr; - IP4::addr daddr; - uint8_t zero; - uint8_t proto; - uint16_t tcp_length; - }__attribute__((packed)); - - /* - TCP Checksum-header (TCP-header + pseudo-header) - */ - struct Checksum_header { - TCP::Pseudo_header pseudo; - TCP::Header tcp; - }__attribute__((packed)); - - /* - To extract the TCP part from a Packet_ptr and calculate size. (?) - */ - struct Full_header { - Ethernet::header ethernet; - IP4::ip_header ip4; - TCP::Header tcp; - }__attribute__((packed)); - - - /* - A Wrapper for a TCP Packet. Is everything as a IP4 Packet, - in addition to the TCP Header and functions to modify this and the control bits (FLAGS). - */ - class Packet : public PacketIP4 { - public: - - inline TCP::Header& header() const - { - return ((TCP::Full_header*) buffer())->tcp; - } - - static const size_t HEADERS_SIZE = sizeof(TCP::Full_header); - - //! initializes to a default, empty TCP packet, given - //! a valid MTU-sized buffer - void init() - { - // Erase all headers (smart? necessary? ...well, convenient) - memset(buffer(), 0, HEADERS_SIZE); - PacketIP4::init(); - - set_protocol(IP4::IP4_TCP); - set_win_size(TCP::default_window_size); - set_offset(5); - - // set TCP payload location (!?) - set_payload(buffer() + all_headers_len()); - } - - // GETTERS - inline TCP::Port src_port() const { return ntohs(header().source_port); } - - inline TCP::Port dst_port() const { return ntohs(header().destination_port); } - - inline TCP::Seq seq() const { return ntohl(header().seq_nr); } - - inline TCP::Seq ack() const { return ntohl(header().ack_nr); } - - inline uint16_t win() const { return ntohs(header().window_size); } - - inline TCP::Socket source() const { return TCP::Socket{src(), src_port()}; } - - inline TCP::Socket destination() const { return TCP::Socket{dst(), dst_port()}; } - - // SETTERS - inline TCP::Packet& set_src_port(TCP::Port p) { - header().source_port = htons(p); - return *this; - } - - inline TCP::Packet& set_dst_port(TCP::Port p) { - header().destination_port = htons(p); - return *this; - } - - inline TCP::Packet& set_seq(TCP::Seq n) { - header().seq_nr = htonl(n); - return *this; - } - - inline TCP::Packet& set_ack(TCP::Seq n) { - header().ack_nr = htonl(n); - return *this; - } - - inline TCP::Packet& set_win_size(uint16_t size) { - header().window_size = htons(size); - return *this; - } - - inline TCP::Packet& set_checksum(uint16_t checksum) { - header().checksum = checksum; - return *this; - } - - inline TCP::Packet& set_source(const TCP::Socket& src) { - set_src(src.address()); // PacketIP4::set_src - set_src_port(src.port()); - return *this; - } - - inline TCP::Packet& set_destination(const TCP::Socket& dest) { - set_dst(dest.address()); // PacketIP4::set_dst - set_dst_port(dest.port()); - return *this; - } - - /// FLAGS / CONTROL BITS /// - - inline TCP::Packet& set_flag(TCP::Flag f) { - header().offset_flags.whole |= htons(f); - return *this; - } - - inline TCP::Packet& set_flags(uint16_t f) { - header().offset_flags.whole |= htons(f); - return *this; - } - - inline TCP::Packet& clear_flag(TCP::Flag f) { - header().offset_flags.whole &= ~ htons(f); - return *this; - } - - inline TCP::Packet& clear_flags() { - header().offset_flags.whole &= 0x00ff; - return *this; - } - - inline bool isset(TCP::Flag f) { return ntohs(header().offset_flags.whole) & f; } - - - /// OFFSET, OPTIONS, DATA /// - - // Get the raw tcp offset, in quadruples - inline uint8_t offset() const { return (uint8_t)(header().offset_flags.offset_reserved >> 4); } - - // Set raw TCP offset in quadruples - inline void set_offset(uint8_t offset) { header().offset_flags.offset_reserved = (offset << 4); } - - // The actaul TCP header size (including options). - inline uint8_t header_size() const { return offset() * 4; } + class Connection : public std::enable_shared_from_this { + friend class TCP; + public: + + /* + Wrapper around a buffer that receives data. + */ + struct ReadBuffer { + buffer_t buffer; + size_t remaining; + size_t offset; + bool push; + + ReadBuffer(buffer_t buf, size_t length, size_t offs = 0) + : buffer(buf), remaining(length-offs), offset(offs), push(false) {} + + inline size_t capacity() const { return remaining + offset; } + + inline bool empty() const { return offset == 0; } + + inline bool full() const { return remaining == 0; } + + inline size_t size() const { return offset; } + + inline uint8_t* begin() const { return buffer.get(); } + + inline uint8_t* pos() const { return buffer.get() + offset; } + + inline uint8_t* end() const { return buffer.get() + capacity(); } + + inline bool advance(size_t length) { + assert(length <= remaining); + offset += length; + remaining -= length; + return length > 0; + } + + void clear() { + memset(begin(), 0, offset); + remaining = capacity(); + offset = 0; + push = false; + } + + /* + Renews the ReadBuffer by assigning a new buffer_t, releasing ownership + */ + inline void renew() { + remaining = capacity(); + offset = 0; + buffer = buffer_t(new uint8_t[remaining], std::default_delete()); + push = false; + } + }; // < Connection::ReadBuffer + + + /* + Wrapper around a buffer that contains data to be written. + */ + struct WriteBuffer { + buffer_t buffer; + size_t remaining; + size_t offset; + size_t acknowledged; + bool push; + + WriteBuffer(buffer_t buf, size_t length, bool PSH, size_t offs = 0) + : buffer(buf), remaining(length-offs), offset(offs), acknowledged(0), push(PSH) {} + + inline size_t length() const { return remaining + offset; } + + inline bool done() const { return acknowledged == length(); } + + inline uint8_t* begin() const { return buffer.get(); } + + inline uint8_t* pos() const { return buffer.get() + offset; } + + inline uint8_t* end() const { return buffer.get() + length(); } + + inline bool advance(size_t length) { + assert(length <= remaining); + offset += length; + remaining -= length; + return length > 0; + } + + size_t acknowledge(size_t bytes) { + auto acked = std::min(bytes, length()-acknowledged); + acknowledged += acked; + return acked; + } + + }; // < Connection::WriteBuffer + + /* + Callback when a receive buffer receives either push or is full + - Supplied on asynchronous read + */ + using ReadCallback = delegate; + + struct ReadRequest { + ReadBuffer buffer; + ReadCallback callback; + + ReadRequest(ReadBuffer buf, ReadCallback cb) : buffer(buf), callback(cb) {} + ReadRequest(size_t n = 0) : + buffer(buffer_t(new uint8_t[n], std::default_delete()), n), + callback([](auto, auto){}) {} + }; + + /* + Callback when a write is sent by the TCP + - Supplied on asynchronous write + */ + using WriteCallback = delegate; + + using WriteRequest = std::pair; + + + /* + Write Queue containig WriteRequests from user. + Stores requests until they are fully acknowledged; + this will make it possible to retransmit + */ + struct WriteQueue { + + std::deque q; + + /* Current element (index + 1) */ + uint32_t current; + + WriteQueue() : q(), current(0) {} + + /* + Acknowledge n bytes from the write queue. + If a Request is fully acknowledged, release from queue + and "step back". + */ + void acknowledge(size_t bytes) { + debug2(" Acknowledge %u bytes\n",bytes); + while(bytes and !q.empty()) + { + auto& buf = q.front().first; + + bytes -= buf.acknowledge(bytes); + if(buf.done()) { + q.pop_front(); + current--; + } + } + } + + bool empty() const + { return q.empty(); } + + size_t size() const + { return q.size(); } + + /* + If the queue has more data to send + */ + bool remaining_requests() const + { return !q.empty() and q.back().first.remaining; } + + /* + The current buffer to write from. + Can be in the middle/back of the queue due to unacknowledged buffers in front. + */ + const WriteBuffer& nxt() + { return q[current-1].first; } + + /* + The oldest unacknowledged buffer. (Always in front) + */ + const WriteBuffer& una() + { return q.front().first; } + + /* + Advances the queue forward. + If current buffer finishes; exec user callback and step to next. + */ + void advance(size_t bytes) { + + auto& buf = q[current-1].first; + buf.advance(bytes); + + debug2(" Advance: bytes=%u off=%u rem=%u ack=%u\n", + bytes, buf.offset, buf.remaining, buf.acknowledged); + + if(!buf.remaining) { + debug(" Advance: Done (%u)\n", + buf.offset); + // make sure to advance current before callback is made, + // but after index (current) is received. + q[current++-1].second(buf.offset); + } + } + + /* + Add a request to the back of the queue. + If the queue was empty/finished, point current to the new request. + */ + void push_back(const WriteRequest& wr) { + q.push_back(wr); + debug(" Inserted WR: off=%u rem=%u ack=%u\n", + wr.first.offset, wr.first.remaining, wr.first.acknowledged); + if(current == q.size()-1) + current++; + } + + /* + Remove all write requests from queue and signal how much was written for each request. + */ + void reset() { + while(!q.empty()) { + auto& req = q.front(); + // only give callbacks on request who hasnt finished writing + // (others has already been called) + if(req.first.remaining > 0) + req.second(req.first.offset); + q.pop_front(); + } + } + }; // < TCP::Connection::WriteQueue + + + /* + Connection identifier + */ + using Tuple = std::pair; + + /// CALLBACKS /// + /* + On connection attempt - When a remote sends SYN to connection in LISTENING state. + First thing that will happen. + */ + using AcceptCallback = delegate)>; + + /* + On connected - When both hosts exchanged sequence numbers (handshake is done). + Now in ESTABLISHED state - it's allowed to write and read to/from the remote. + */ + using ConnectCallback = delegate)>; + + /* + On disconnect - When a remote told it wanna close the connection. + Connection has received a FIN, currently last thing that will happen before a connection is remoed. + */ + struct Disconnect; + + // TODO: Remove reference to Connection, probably not needed.. + using DisconnectCallback = delegate, Disconnect)>; + + /* + On error - When any of the users request fails. + */ + using ErrorCallback = delegate, TCPException)>; + + /* + When a packet is received - Everytime a connection receives an incoming packet. + Would probably be used for debugging. + (Currently not in use) + */ + using PacketReceivedCallback = delegate, TCP::Packet_ptr)>; + + /* + When a packet is dropped - Everytime an incoming packet is unallowed, it will be dropped. + Can be used for debugging. + */ + using PacketDroppedCallback = delegate; + + + /* + Reason for disconnect event. + */ + struct Disconnect { + public: + enum Reason { + CLOSING, + REFUSED, + RESET + }; + + Reason reason; + + explicit Disconnect(Reason reason) : reason(reason) {} + + inline operator Reason() const noexcept { return reason; } + + inline operator std::string() const noexcept { return to_string(); } + + inline bool operator ==(const Disconnect& dc) const { return reason == dc.reason; } + + std::string to_string() const { + switch(reason) { + case CLOSING: + return "Connection closing"; + case REFUSED: + return "Connection refused"; + case RESET: + return "Connection reset"; + default: + return "Unknown reason"; + } + } + }; // < struct TCP::Connection::Disconnect + + /* + Interface for one of the many states a Connection can have. + */ + class State { + public: + enum Result { + CLOSED = -1, // This inditactes that a Connection is done and should be closed. + OK = 0, // Does nothing + CLOSE = 1 // This indicates that the CLIENT (peer) has/wants to close their end. + }; + /* + Open a Connection. + OPEN + */ + virtual void open(Connection&, bool active = false); + + /* + Write to a Connection. + SEND + */ + virtual size_t send(Connection&, WriteBuffer&); + + /* + Read from a Connection. + RECEIVE + */ + virtual void receive(Connection&, ReadBuffer&); + + /* + Close a Connection. + CLOSE + */ + virtual void close(Connection&); + + /* + Terminate a Connection. + ABORT + */ + virtual void abort(Connection&); + + /* + Handle a Packet + SEGMENT ARRIVES + */ + virtual Result handle(Connection&, TCP::Packet_ptr in) = 0; + + /* + The current state represented as a string. + STATUS + */ + virtual std::string to_string() const = 0; + + /* + + */ + virtual bool is_connected() const { return false; } + + virtual bool is_writable() const { return false; } + + virtual bool is_readable() const { return false; } + + virtual bool is_closing() const { return false; } + + protected: + /* + Helper functions + TODO: Clean up names. + */ + virtual bool check_seq(Connection&, TCP::Packet_ptr); + + virtual void unallowed_syn_reset_connection(Connection&, TCP::Packet_ptr); + + virtual bool check_ack(Connection&, TCP::Packet_ptr); + + virtual void process_segment(Connection&, TCP::Packet_ptr); + + virtual void process_fin(Connection&, TCP::Packet_ptr); + + virtual void send_reset(Connection&); + + }; // < class TCP::Connection::State + + /* + Forward declaration of concrete states. + Definition in "tcp_connection_states.hpp" + */ + class Closed; + class Listen; + class SynSent; + class SynReceived; + class Established; + class FinWait1; + class FinWait2; + class CloseWait; + class Closing; + class LastAck; + class TimeWait; + + /* + Transmission Control Block. + Keep tracks of all the data for a connection. + + RFC 793: Page 19 + Among the variables stored in the + TCB are the local and remote socket numbers, the security and + precedence of the connection, pointers to the user's send and receive + buffers, pointers to the retransmit queue and to the current segment. + In addition several variables relating to the send and receive + sequence numbers are stored in the TCB. + */ + struct TCB { + /* Send Sequence Variables */ + struct { + TCP::Seq UNA; // send unacknowledged + TCP::Seq NXT; // send next + uint16_t WND; // send window + uint16_t UP; // send urgent pointer + TCP::Seq WL1; // segment sequence number used for last window update + TCP::Seq WL2; // segment acknowledgment number used for last window update + + uint16_t MSS; // Maximum segment size for outgoing segments. + } SND; // << + TCP::Seq ISS; // initial send sequence number + + /* Receive Sequence Variables */ + struct { + TCP::Seq NXT; // receive next + uint16_t WND; // receive window + uint16_t UP; // receive urgent pointer + + uint16_t rwnd; // receivers advertised window [RFC 5681] + } RCV; // << + TCP::Seq IRS; // initial receive sequence number + + uint32_t ssthresh; // slow start threshold [RFC 5681] + uint32_t cwnd; // Congestion window [RFC 5681] + Seq recover; // New Reno [RFC 6582] + + TCB() { + SND = { 0, 0, TCP::default_window_size, 0, 0, 0, TCP::default_mss }; + ISS = (Seq)4815162342; + RCV = { 0, TCP::default_window_size, 0, 0 }; + IRS = 0; + ssthresh = TCP::default_window_size; + cwnd = 0; + recover = 0; + }; + + void init() { + ISS = TCP::generate_iss(); + recover = ISS; // [RFC 6582] + } + + bool slow_start() { + return cwnd < ssthresh; + } + + std::string to_string() const; + }__attribute__((packed)); // < struct TCP::Connection::TCB + + /* + Creates a connection without a remote. + */ + Connection(TCP& host, Port local_port); + + /* + Creates a connection with a remote. + */ + Connection(TCP& host, Port local_port, Socket remote); + + /* + The hosting TCP instance. + */ + inline const TCP& host() const { return host_; } + + /* + The local Port bound to this connection. + */ + inline TCP::Port local_port() const + { return local_port_; } + + /* + The local Socket bound to this connection. + */ + inline TCP::Socket local() const { return {host_.inet_.ip_addr(), local_port_}; } + + /* + The remote Socket bound to this connection. + */ + inline TCP::Socket remote() const { return remote_; } + + /* + Set remote Socket bound to this connection. + @WARNING: Should this be public? Used by TCP. + */ + inline void set_remote(Socket remote) { remote_ = remote; } + + + /* + Read asynchronous from a remote. + + Create n sized internal read buffer and callback for when data is received. + Callback will be called until overwritten with a new read() or connection closes. + Buffer is cleared for data after every reset. + */ + inline void read(size_t n, ReadCallback callback) { + ReadBuffer buffer = {buffer_t(new uint8_t[n], std::default_delete()), n}; + read(buffer, callback); + } + + /* + Assign the connections receive buffer and callback for when data is received. + Works as read(size_t, ReadCallback); + */ + inline void read(buffer_t buffer, size_t n, ReadCallback callback) { + read({buffer, n}, callback); + } + + void read(ReadBuffer buffer, ReadCallback callback); + + + /* + Write asynchronous to a remote. + + Copies the data from the buffer into an internal buffer. Callback is called when a a write is either done or aborted. + Immediately tries to write the data to the connection. If not possible, queues the write for processing when possible (FIFO). + */ + inline void write(const void* buf, size_t n, WriteCallback callback, bool PUSH) { + auto buffer = buffer_t(new uint8_t[n], std::default_delete()); + memcpy(buffer.get(), buf, n); + write(buffer, n, callback, PUSH); + } + + inline void write(const void* buf, size_t n, WriteCallback callback) + { write(buf, n, callback, true); } + + // results in ambiguous call to member function + //inline void write(const void* buf, size_t n, bool PUSH) + //{ write(buf, n, WriteCallback::from(this), PUSH); } + + inline void write(const void* buf, size_t n) + { write(buf, n, WriteCallback::from(this), true); } + + /* + Works as write(const void*, size_t, WriteCallback, bool), + but with the exception of avoiding copying the data to an internal buffer. + */ + inline void write(buffer_t buffer, size_t n, WriteCallback callback, bool PUSH) + { write({buffer, n, PUSH}, callback); } + + inline void write(buffer_t buffer, size_t n, WriteCallback callback) + { write({buffer, n, true}, callback); } + + // results in ambiguous call to member function + //inline void write(buffer_t buffer, size_t n, bool PUSH) + //{ write({buffer, n, PUSH}, WriteCallback::from(this)); } + + inline void write(buffer_t buffer, size_t n) + { write({buffer, n, true}, WriteCallback::from(this)); } + + /* + Write a WriteBuffer asynchronous to a remote and calls the WriteCallback when done (or aborted). + */ + void write(WriteBuffer request, WriteCallback callback); + + inline void default_on_write(size_t) {}; + + + /* + Open connection. + */ + void open(bool active = false); + + /* + Close connection. + */ + void close(); + + /* + Abort connection. (Same as Terminate) + */ + inline void abort() { + state_->abort(*this); + signal_close(); + } + + /* + Set callback for ACCEPT event. + */ + inline Connection& onAccept(AcceptCallback callback) { + on_accept_ = callback; + return *this; + } + + /* + Set callback for CONNECT event. + */ + inline Connection& onConnect(ConnectCallback callback) { + on_connect_ = callback; + return *this; + } + + /* + Set callback for DISCONNECT event. + */ + inline Connection& onDisconnect(DisconnectCallback callback) { + on_disconnect_ = callback; + return *this; + } + + /* + Set callback for ERROR event. + */ + inline Connection& onError(ErrorCallback callback) { + on_error_ = callback; + return *this; + } + + /* + Set callback for every packet received. + */ + inline Connection& onPacketReceived(PacketReceivedCallback callback) { + on_packet_received_ = callback; + return *this; + } + + /* + Set callback for when a packet is dropped. + */ + inline Connection& onPacketDropped(PacketDroppedCallback callback) { + on_packet_dropped_ = callback; + return *this; + } + + /* + Represent the Connection as a string (STATUS). + */ + std::string to_string() const; + + /* + Returns the current state of the connection. + */ + inline Connection::State& state() const { return *state_; } + + /* + Returns the previous state of the connection. + */ + inline Connection::State& prev_state() const { return *prev_state_; } + + /* + Calculates and return bytes transmitted. + TODO: Not sure if this will suffice. + */ + inline uint32_t bytes_transmitted() const { + return cb.SND.NXT - cb.ISS; + } + + /* + Calculates and return bytes received. + TODO: Not sure if this will suffice. + */ + inline uint32_t bytes_received() const { + return cb.RCV.NXT - cb.IRS; + } + + /* + Bytes queued for transmission. + TODO: Implement when retransmission is up and running. + */ + //inline size_t send_queue_bytes() const {} + + /* + Bytes currently in receive buffer. + */ + inline size_t read_queue_bytes() const { + return read_request.buffer.size(); + } + + /* + Return the id (TUPLE) of the connection. + */ + inline Connection::Tuple tuple() const { + return {local_port_, remote_}; + } + + + /* + State checks. + */ + bool is_listening() const; + + inline bool is_connected() const { return state_->is_connected(); } + + inline bool is_writable() const { return state_->is_writable(); } + + inline bool is_readable() const { return state_->is_readable(); } + + inline bool is_closing() const { return state_->is_closing(); } + + + + /* + Helper function for state checks. + */ + inline bool is_state(const State& state) const { + return state_ == &state; + } + + inline bool is_state(const std::string& state_str) const { + return state_->to_string() == state_str; + } + + /* + Destroy the Connection. + + Clean up. + */ + ~Connection(); + + private: + /* + "Parent" for Connection. + */ + TCP& host_; // 4 B + + /* + End points. + */ + TCP::Port local_port_; // 2 B + TCP::Socket remote_; // 8~ B + + /* + The current state the Connection is in. + Handles most of the logic. + */ + State* state_; // 4 B + // Previous state. Used to keep track of state transitions. + State* prev_state_; // 4 B + + /* + Keep tracks of all sequence variables. + */ + TCB cb; // 36 B + + /* + The given read request + */ + ReadRequest read_request; + + /* + Queue for write requests to process + */ + WriteQueue writeq; + + /* + State if connection is in TCP write queue or not. + */ + bool queued_; + + struct { + hw::PIT::Timer_iterator iter; + bool active = false; + size_t i = 0; + } rtx_timer; + + /* + When time-wait timer was started. + */ + uint64_t time_wait_started; + + + + // [RFC 6298] + // Round Trip Time Measurer + struct RTTM { + using timestamp_t = double; + using duration_t = double; + + // clock granularity + //static constexpr duration_t CLOCK_G = hw::PIT::frequency().count() / 1000; + static constexpr duration_t CLOCK_G = 0.0011; + + static constexpr double K = 4.0; + + static constexpr double alpha = 1.0/8; + static constexpr double beta = 1.0/4; + + timestamp_t t; // tick when measure is started + + duration_t SRTT; // smoothed round-trip time + duration_t RTTVAR; // round-trip time variation + duration_t RTO; // retransmission timeout + + bool active = false; + + RTTM() : t(OS::uptime()), SRTT(1.0), RTTVAR(1.0), RTO(1.0), active(false) {} + + void start() { + t = OS::uptime(); + active = true; + } + + void stop(bool first = false) { + assert(active); + active = false; + // round trip time (RTT) + auto rtt = OS::uptime() - t; + debug2(" RTT: %ums\n", + (uint32_t)(rtt * 1000)); + if(!first) + sub_rtt_measurement(rtt); + else { + first_rtt_measurement(rtt); + } + } + + /* + When the first RTT measurement R is made, the host MUST set + + SRTT <- R + RTTVAR <- R/2 + RTO <- SRTT + max (G, K*RTTVAR) + + where K = 4. + */ + inline void first_rtt_measurement(duration_t R) { + SRTT = R; + RTTVAR = R/2; + update_rto(); + } + + /* + When a subsequent RTT measurement R' is made, a host MUST set + + RTTVAR <- (1 - beta) * RTTVAR + beta * |SRTT - R'| + SRTT <- (1 - alpha) * SRTT + alpha * R' + + The value of SRTT used in the update to RTTVAR is its value + before updating SRTT itself using the second assignment. That + is, updating RTTVAR and SRTT MUST be computed in the above + order. + + The above SHOULD be computed using alpha=1/8 and beta=1/4 (as + suggested in [JK88]). + + After the computation, a host MUST update + RTO <- SRTT + max (G, K*RTTVAR) + */ + inline void sub_rtt_measurement(duration_t R) { + RTTVAR = (1 - beta) * RTTVAR + beta * std::abs(SRTT-R); + SRTT = (1 - alpha) * SRTT + alpha * R; + update_rto(); + } + + inline void update_rto() { + RTO = std::max(SRTT + std::max(CLOCK_G, K * RTTVAR), 1.0); + debug2(" RTO updated: %ums\n", + (uint32_t)(RTO * 1000)); + } + + } rttm; + + + /// CALLBACK HANDLING /// + + /* When a Connection is initiated. */ + AcceptCallback on_accept_ = AcceptCallback::from(this); + inline bool default_on_accept(std::shared_ptr) { + //debug2(" Connection attempt from: %s \n", conn->remote().to_string().c_str()); + return true; // Always accept + } + + /* When Connection is ESTABLISHED. */ + ConnectCallback on_connect_ = [](std::shared_ptr) { + debug2(" Connected.\n"); + }; + + /* When Connection is CLOSING. */ + DisconnectCallback on_disconnect_ = [](std::shared_ptr, Disconnect) { + //debug2(" Connection disconnect. Reason: %s \n", msg.c_str()); + }; + + /* When error occcured. */ + ErrorCallback on_error_ = ErrorCallback::from(this); + inline void default_on_error(std::shared_ptr, TCPException) { + //debug2(" TCPException: %s \n", error.what()); + } + + /* When packet is received */ + PacketReceivedCallback on_packet_received_ = [](std::shared_ptr, TCP::Packet_ptr) { + //debug2(" Packet received: %s \n", packet->to_string().c_str()); + }; + + /* When a packet is dropped. */ + PacketDroppedCallback on_packet_dropped_ = [](TCP::Packet_ptr, std::string) { + //debug(" Packet dropped. %s | Reason: %s \n", + // packet->to_string().c_str(), reason.c_str()); + }; + + + /// READING /// + + /* + Assign the read request (read buffer) + */ + inline void receive(ReadBuffer& buffer) { + read_request.buffer = {buffer}; + } + + /* + Receive data into the current read requests buffer. + */ + size_t receive(const uint8_t* data, size_t n, bool PUSH); + + /* + Copy data into the ReadBuffer + */ + inline size_t receive(ReadBuffer& buf, const uint8_t* data, size_t n) { + auto received = std::min(n, buf.remaining); + memcpy(buf.pos(), data, received); // Can we use move? + return received; + } + + /* + Remote is closing, no more data will be received. + Returns receive buffer to user. + */ + inline void receive_disconnect() { + assert(!read_request.buffer.empty()); + auto& buf = read_request.buffer; + buf.push = true; + read_request.callback(buf.buffer, buf.size()); + } + + + /// WRITING /// + + /* + Active try to send a buffer by asking the TCP. + */ + inline size_t send(WriteBuffer& buffer) { + return host_.send(shared_from_this(), (char*)buffer.pos(), buffer.remaining); + } + + /* + Segmentize buffer into packets until either everything has been written, + or all packets are used up. + */ + size_t send(const char* buffer, size_t remaining, size_t& packets); + + inline size_t send(WriteBuffer& buffer, size_t& packets, size_t n) { + return send((char*)buffer.pos(), n, packets); + } + + /* + Process the write queue with the given amount of packets. + */ + void offer(size_t& packets); + + /* + Returns if the connection has a doable write job. + */ + inline bool has_doable_job() { + return writeq.remaining_requests() and usable_window() >= SMSS(); + } + + /* + Try to process the current write queue. + */ + void writeq_push(); + + /* + Try to write (some of) queue on connected. + */ + inline void writeq_on_connect() { writeq_push(); } + + /* + Reset queue on disconnect. Clears the queue and notice every requests callback. + */ + void writeq_reset(); + + /* + Returns if the TCP has the Connection in write queue + */ + inline bool is_queued() const { + return queued_; + } + + /* + Mark wether the Connection is in TCP write queue or not. + */ + inline void set_queued(bool queued) { + queued_ = queued; + } + + /* + Invoke/signal the diffrent TCP events. + */ + inline bool signal_accept() { return on_accept_(shared_from_this()); } + + inline void signal_connect() { on_connect_(shared_from_this()); } + + inline void signal_disconnect(Disconnect::Reason&& reason) { on_disconnect_(shared_from_this(), Disconnect{reason}); } + + inline void signal_error(TCPException error) { on_error_(shared_from_this(), error); } + + inline void signal_packet_received(TCP::Packet_ptr packet) { on_packet_received_(shared_from_this(), packet); } + + inline void signal_packet_dropped(TCP::Packet_ptr packet, std::string reason) { on_packet_dropped_(packet, reason); } + + /* + Drop a packet. Used for debug/callback. + */ + inline void drop(TCP::Packet_ptr packet, std::string reason) { signal_packet_dropped(packet, reason); } + inline void drop(TCP::Packet_ptr packet) { drop(packet, "None given."); } + + + // RFC 3042 + void limited_tx(); + + /// TCB HANDLING /// + + /* + Returns the TCB. + */ + inline Connection::TCB& tcb() { return cb; } + + /* + + SND.UNA + SND.WND - SND.NXT + SND.UNA + WINDOW - SND.NXT + */ + inline uint32_t usable_window() const { + auto x = (int64_t)send_window() - (int64_t)flight_size(); + return (uint32_t) std::max(0ll, x); + } + + /* + + Note: + Made a function due to future use when Window Scaling Option is added. + */ + inline uint32_t send_window() const { + return std::min((uint32_t)cb.SND.WND, cb.cwnd); + } + + inline int32_t congestion_window() const { + auto win = (uint64_t)cb.SND.UNA + std::min((uint64_t)cb.cwnd, (uint64_t)send_window()); + return (int32_t)win; + } + + /* + Acknowledge a packet + - TCB update, Congestion control handling, RTT calculation and RT handling. + */ + bool handle_ack(TCP::Packet_ptr); + + /* + When a duplicate ACK is received. + */ + void on_dup_ack(); + + /* + Is it possible to send ONE segment. + */ + bool can_send_one(); + + /* + Is the usable window large enough, and is there data to send. + */ + bool can_send(); + + /* + Send as much as possible from write queue. + */ + void send_much(); + + /* + Fill a packet with data and give it a SEQ number. + */ + size_t fill_packet(Packet_ptr, const char*, size_t, Seq); + + /// Congestion Control [RFC 5681] /// + + // is fast recovery state + bool fast_recovery = false; - // Calculate the full header length, down to linklayer, in bytes - uint8_t all_headers_len() const { return (HEADERS_SIZE - sizeof(TCP::Header)) + header_size(); } - - // Where data starts - inline char* data() { return (char*) (buffer() + all_headers_len()); } - - inline uint16_t data_length() const { return size() - all_headers_len(); } - - inline bool has_data() const { return data_length() > 0; } - - inline uint16_t tcp_length() const { return header_size() + data_length(); } - - - - // sets the correct length for all the protocols up to IP4 - void set_length(uint16_t newlen) { - // new total packet length - set_size( all_headers_len() + newlen ); - } - - //! assuming the packet has been properly initialized, - //! this will fill bytes from @buffer into this packets buffer, - //! then return the number of bytes written. buffer is unmodified - size_t fill(const char* buffer, size_t length) { - size_t rem = capacity() - all_headers_len(); - size_t total = (length < rem) ? length : rem; - // copy from buffer to packet buffer - memcpy(data() + data_length(), buffer, total); - // set new packet length - set_length(data_length() + total); - return total; - } - - inline std::string to_string() { - std::ostringstream os; - os << "[ S:" << source().to_string() << " D:" << destination().to_string() - << " SEQ:" << seq() << " ACK:" << ack() << " HEAD-LEN:" << (int)header_size() <<" DATA-LEN:" << data_length() - << " WIN:" << win() << " FLAGS:" << std::bitset<8>{header().offset_flags.flags} << " ]"; - return os.str(); - } - - }; // << class TCP::Packet - - /* - TODO: Implement. - */ - class TCPException : public std::runtime_error { - public: - TCPException(std::string error) : std::runtime_error(error) {}; - }; - - /* - Buffer for TCP::Packet_ptr - */ - template> - class PacketBuffer { - public: - - PacketBuffer(typename Buffer::size_type limit) : - buffer_(), data_length_(0), - data_offset_(0), limit_(limit) - { - - } - /* Number of packets */ - inline auto size() const { return buffer_.size(); } - - /* Amount of data */ - inline size_t data_size() const { return data_length_; } - - inline void push(const T& packet) { - buffer_.push(packet); - data_length_ += (size_t)packet->data_length(); - } - - inline bool add(T packet) { - if(full()) return false; - push(packet); - return true; - } - - inline void pop() { - data_length_ -= (size_t)buffer_.front()->data_length(); - buffer_.pop(); - } - - inline const T& front() const { return buffer_.front(); } - - inline const T& back() const { return buffer_.back(); } - - inline bool empty() const { return buffer_.empty(); } - - inline auto limit() const { return limit_; } - - inline bool full() const { return size() >= limit(); } - - inline auto data_offset() const { return data_offset_; } - - inline void set_data_offset(uint32_t offset) { data_offset_ = offset; } - - inline void clear() { - while(!buffer_.empty()) - buffer_.pop(); - data_length_ = {0}; - data_offset_ = {0}; - } - - private: - Buffer buffer_; - size_t data_length_; - uint32_t data_offset_; - typename Buffer::size_type limit_; - - }; // << TCP::PacketBuffer - - - /* - A connection between two Sockets (local and remote). - Receives and handle TCP::Packet. - Transist between many states. - */ - class Connection : public std::enable_shared_from_this { - public: - /* - Connection identifier - */ - using Tuple = std::pair; - - /// CALLBACKS /// - /* - On connection attempt - When a remote sends SYN to connection in LISTENING state. - First thing that will happen. - */ - using AcceptCallback = delegate)>; - - /* - On connected - When both hosts exchanged sequence numbers (handshake is done). - Now in ESTABLISHED state - it's allowed to write and read to/from the remote. - */ - using ConnectCallback = delegate)>; - - /* - On receiving data - When there is data to read in the receive buffer. - Either when remote PUSH, or buffer is full. - */ - using ReceiveCallback = delegate, bool)>; - - /* - On disconnect - When a remote told it wanna close the connection. - Connection has received a FIN, currently last thing that will happen before a connection is remoed. - */ - using DisconnectCallback = delegate, std::string)>; - - /* - On error - When any of the users request fails. - */ - using ErrorCallback = delegate, TCPException)>; - - /* - When a packet is received - Everytime a connection receives an incoming packet. - Would probably be used for debugging. - (Currently not in use) - */ - using PacketReceivedCallback = delegate, TCP::Packet_ptr)>; - - /* - When a packet is dropped - Everytime an incoming packet is unallowed, it will be dropped. - Can be used for debugging. - */ - using PacketDroppedCallback = delegate; - - /* - Buffer - */ - using Buffer = PacketBuffer<>; - - /* - Interface for one of the many states a Connection can have. - */ - class State { - public: - enum Result { - CLOSED = -1, - OK = 0, - CLOSE = 1 - }; - /* - Open a Connection. - OPEN - */ - virtual void open(Connection&, bool active = false); - - /* - Write to a Connection. - SEND - */ - virtual size_t send(Connection&, const char* buffer, size_t n, bool push = false); - - /* - Read from a Connection. - RECEIVE - */ - virtual size_t receive(Connection&, char* buffer, size_t n); - - /* - Close a Connection. - CLOSE - */ - virtual void close(Connection&); - - /* - Terminate a Connection. - ABORT - */ - virtual void abort(Connection&); - - /* - Handle a Packet - SEGMENT ARRIVES - */ - virtual Result handle(Connection&, TCP::Packet_ptr in) = 0; - - /* - The current state represented as a string. - STATUS - */ - virtual std::string to_string() const = 0; - - protected: - /* - Helper functions - TODO: Clean up names. - */ - virtual bool check_seq(Connection&, TCP::Packet_ptr); - - virtual void unallowed_syn_reset_connection(Connection&, TCP::Packet_ptr); - - virtual bool check_ack(Connection&, TCP::Packet_ptr); - - virtual void process_segment(Connection&, TCP::Packet_ptr); - - virtual void process_fin(Connection&, TCP::Packet_ptr); - - virtual void send_reset(Connection&); - - }; // < class TCP::Connection::State - - /* - Forward declaration of concrete states. - Definition in "tcp_connection_states.hpp" - */ - class Closed; - class Listen; - class SynSent; - class SynReceived; - class Established; - class FinWait1; - class FinWait2; - class CloseWait; - class LastAck; - class Closing; - class TimeWait; - - /* - Transmission Control Block. - Keep tracks of all the data for a connection. - - RFC 793: Page 19 - Among the variables stored in the - TCB are the local and remote socket numbers, the security and - precedence of the connection, pointers to the user's send and receive - buffers, pointers to the retransmit queue and to the current segment. - In addition several variables relating to the send and receive - sequence numbers are stored in the TCB. - */ - struct TCB { - /* Send Sequence Variables */ - struct { - TCP::Seq UNA; // send unacknowledged - TCP::Seq NXT; // send next - uint16_t WND; // send window - uint16_t UP; // send urgent pointer - TCP::Seq WL1; // segment sequence number used for last window update - TCP::Seq WL2; // segment acknowledgment number used for last window update - } SND; // << - TCP::Seq ISS; // initial send sequence number - - /* Receive Sequence Variables */ - struct { - TCP::Seq NXT; // receive next - uint16_t WND; // receive window - uint16_t UP; // receive urgent pointer - } RCV; // << - TCP::Seq IRS; // initial receive sequence number - - TCB() { - SND = { 0, 0, TCP::default_window_size, 0, 0, 0 }; - ISS = 0; - RCV = { 0, TCP::default_window_size, 0 }; - IRS = 0; - }; - - std::string to_string() const; - }__attribute__((packed)); // < struct TCP::Connection::TCB - - /* - Creates a connection without a remote. - */ - Connection(TCP& host, Port local_port); - - /* - Creates a connection with a remote. - */ - Connection(TCP& host, Port local_port, Socket remote); - - /* - The hosting TCP instance. - */ - inline const TCP& host() const { return host_; } - - /* - The local Socket bound to this connection. - */ - inline TCP::Socket local() const { return {host_.inet_.ip_addr(), local_port_}; } - - /* - The remote Socket bound to this connection. - */ - inline TCP::Socket remote() const { return remote_; } - - /* - Set remote Socket bound to this connection. - @WARNING: Should this be public? Used by TCP. - */ - inline void set_remote(Socket remote) { remote_ = remote; } - - - /* - Read content from remote. - */ - size_t read(char* buffer, size_t n); - - /* - Read n bytes into a string. - Default 1024 bytes. - */ - std::string read(size_t n = 0); - - /* - Write content to remote. - */ - size_t write(const char* buffer, size_t n, bool PUSH = true); - - /* - Write a string to the remote. - */ - inline void write(const std::string& content) { - write(content.data(), content.size(), true); - } - - /* - Open connection. - */ - void open(bool active = false); - - /* - Close connection. - */ - void close(); - - /* - Abort connection. (Same as Terminate) - */ - inline void abort() { - state_->abort(*this); - signal_close(); - } - - /* - Set callback for ACCEPT event. - */ - inline Connection& onAccept(AcceptCallback callback) { - on_accept_ = callback; - return *this; - } - - /* - Set callback for CONNECT event. - */ - inline Connection& onConnect(ConnectCallback callback) { - on_connect_ = callback; - return *this; - } - - /* - Set callback for ON RECEIVE event. - */ - inline Connection& onReceive(ReceiveCallback callback) { - on_receive_ = callback; - return *this; - } - - /* - Set callback for DISCONNECT event. - */ - inline Connection& onDisconnect(DisconnectCallback callback) { - on_disconnect_ = callback; - return *this; - } - - /* - Set callback for ERROR event. - */ - inline Connection& onError(ErrorCallback callback) { - on_error_ = callback; - return *this; - } - - /* - Set callback for every packet received. - */ - inline Connection& onPacketReceived(PacketReceivedCallback callback) { - on_packet_received_ = callback; - return *this; - } - - /* - Set callback for when a packet is dropped. - */ - inline Connection& onPacketDropped(PacketDroppedCallback callback) { - on_packet_dropped_ = callback; - return *this; - } - - /* - Represent the Connection as a string (STATUS). - */ - std::string to_string() const; - - /* - Returns the current state of the connection. - */ - inline Connection::State& state() const { return *state_; } - - /* - Returns the previous state of the connection. - */ - inline Connection::State& prev_state() const { return *prev_state_; } - - /* - Calculates and return bytes transmitted. - */ - inline uint32_t bytes_transmitted() const { - return control_block.SND.NXT - control_block.ISS; - } - - /* - Calculates and return bytes received. - */ - inline uint32_t bytes_received() const { - return control_block.RCV.NXT - control_block.IRS; - } - - /* - Return the id (TUPLE) of the connection. - */ - inline Connection::Tuple tuple() const { - return {local_port_, remote_}; - } - - /* - Receive buffer - */ - inline const Buffer& receive_buffer() { - return receive_buffer_; - } - - /* - Send buffer - */ - inline const Buffer& send_buffer() { - return send_buffer_; - } - - /* - Receive a TCP Packet. - - @WARNING: Public, for use in TCP::bottom (friend it?) - */ - void receive(TCP::Packet_ptr); - - - /* - State checks. - */ - bool is_listening() const; - - bool is_connected() const; - - bool is_closing() const; - - bool is_writable() const; - - /* - Helper function for state checks. - */ - inline bool is_state(const State& state) const { - return state_ == &state; - } - - inline bool is_state(const std::string& state_str) const { - return state_->to_string() == state_str; - } - - /* - Destroy the Connection. - - Clean up. - */ - ~Connection(); - - private: - /* - "Parent" for Connection. - */ - TCP& host_; // 4 B - - /* - End points. - */ - TCP::Port local_port_; // 2 B - TCP::Socket remote_; // 8~ B - - /* - The current state the Connection is in. - Handles most of the logic. - */ - State* state_; // 4 B - // Previous state. Used to keep track of state transitions. - State* prev_state_; // 4 B - - /* - Keep tracks of all sequence variables. - */ - TCB control_block; // 36 B - - /* - Buffers - */ - Buffer receive_buffer_; - Buffer send_buffer_; - - /* - When time-wait timer was started. - */ - uint64_t time_wait_started; - - - /// CALLBACK HANDLING /// - - /* When a Connection is initiated. */ - AcceptCallback on_accept_ = AcceptCallback::from(this); - inline bool default_on_accept(std::shared_ptr) { - //debug2(" Connection attempt from: %s \n", conn->remote().to_string().c_str()); - return true; // Always accept - } - - /* When Connection is ESTABLISHED. */ - ConnectCallback on_connect_ = [](std::shared_ptr) { - debug2(" Connected.\n"); - }; - - /* When data is received */ - ReceiveCallback on_receive_ = [](std::shared_ptr, bool) { - debug2(" Connection received data. \n"); - }; - - /* When Connection is CLOSING. */ - DisconnectCallback on_disconnect_ = [](std::shared_ptr, std::string) { - //debug2(" Connection disconnect. Reason: %s \n", msg.c_str()); - }; - - /* When error occcured. */ - ErrorCallback on_error_ = ErrorCallback::from(this); - inline void default_on_error(std::shared_ptr, TCPException) { - //debug2(" TCPException: %s \n", error.what()); - } - - /* When packet is received */ - PacketReceivedCallback on_packet_received_ = [](std::shared_ptr, TCP::Packet_ptr) { - //debug2(" Packet received: %s \n", packet->to_string().c_str()); - }; - - /* When a packet is dropped. */ - PacketDroppedCallback on_packet_dropped_ = [](TCP::Packet_ptr, std::string) { - //debug(" Packet dropped. %s | Reason: %s \n", - // packet->to_string().c_str(), reason.c_str()); - }; - - /* - Invoke/signal the diffrent TCP events. - */ - inline bool signal_accept() { return on_accept_(shared_from_this()); } - - inline void signal_connect() { on_connect_(shared_from_this()); } - - inline void signal_receive(bool PUSH) { on_receive_(shared_from_this(), PUSH); } - - inline void signal_disconnect(std::string message) { on_disconnect_(shared_from_this(), message); } - - inline void signal_error(TCPException error) { on_error_(shared_from_this(), error); } - - inline void signal_packet_received(TCP::Packet_ptr packet) { on_packet_received_(shared_from_this(), packet); } - - inline void signal_packet_dropped(TCP::Packet_ptr packet, std::string reason) { on_packet_dropped_(packet, reason); } - - /* - Drop a packet. Used for debug/callback. - */ - inline void drop(TCP::Packet_ptr packet, std::string reason) { signal_packet_dropped(packet, reason); } - inline void drop(TCP::Packet_ptr packet) { drop(packet, "None given."); } - - - /// TCB HANDLING /// - - /* - Returns the TCB. - */ - inline Connection::TCB& tcb() { return control_block; } - - /* - Generate a new ISS. - */ - TCP::Seq generate_iss(); - - - /// STATE HANDLING /// - /* - Set state. (used by substates) - */ - void set_state(State& state); - - - /// BUFFER HANDLING /// - - /* - Read from receive buffer. - */ - size_t read_from_receive_buffer(char* buffer, size_t n); - - /* - Add(queue) a packet to the receive buffer. - */ - bool add_to_receive_buffer(TCP::Packet_ptr packet); - - /* - Write to the send buffer. Segmentize into packets. - */ - size_t write_to_send_buffer(const char* buffer, size_t n, bool PUSH = true); - - /* - Transmit the send buffer. - */ - void transmit(); - - /* - Transmit the packet. - */ - void transmit(TCP::Packet_ptr); - - /* - Creates a new outgoing packet and put it in the back of the send buffer. - */ - TCP::Packet_ptr create_outgoing_packet(); - - /* - Returns the packet in the back of the send buffer. - If the send buffer is empty, it creates a new packet and adds it. - */ - TCP::Packet_ptr outgoing_packet(); - - - /// RETRANSMISSION /// - - /* - Starts a retransmission timer that retransmits the packet when RTO has passed. - - // TODO: Calculate RTO, currently hardcoded to 1 second (1000ms). - */ - void add_retransmission(TCP::Packet_ptr); - - /* - Measure the elapsed time between sending a data octet with a - particular sequence number and receiving an acknowledgment that - covers that sequence number (segments sent do not have to match - segments received). This measured elapsed time is the Round Trip - Time (RTT). - */ - //std::chrono::milliseconds RTT() const; - std::chrono::milliseconds RTO() const; - - void start_time_wait_timeout(); - - void signal_close(); - - }; // < class TCP::Connection - - - /// USER INTERFACE - TCP /// - - /* - Constructor - */ - TCP(IPStack&); - - /* - Bind a new listener to a given Port. - */ - TCP::Connection& bind(Port port); - - /* - Active open a new connection to the given remote. - */ - Connection_ptr connect(Socket remote); - - /* - Active open a new connection to the given remote. - */ - inline auto connect(TCP::Address address, Port port = 80) { - return connect({address, port}); - } - - /* - Active open a new connection to the given remote. - */ - void connect(Socket remote, Connection::ConnectCallback); - - /* - Receive packet from network layer (IP). - */ - void bottom(net::Packet_ptr); - - /* - Delegate output to network layer - */ - inline void set_network_out(downstream del) { _network_layer_out = del; } - - /* - Compute the TCP checksum - */ + // First partial ack seen + bool reno_fpack_seen = false; + + // limited transmit [RFC 3042] active + bool limited_tx_ = true; + + // number of duplicate acks + size_t dup_acks_ = 0; + + Seq prev_highest_ack_ = 0; + Seq highest_ack_ = 0; + + // number of non duplicate acks received + size_t acks_rcvd_ = 0; + + inline void setup_congestion_control() + { reno_init(); } + + inline uint16_t SMSS() const + { return host_.MSS(); } + + inline uint16_t RMSS() const + { return cb.SND.MSS; } + + inline uint32_t flight_size() const + { return (uint64_t)cb.SND.NXT - (uint64_t)cb.SND.UNA; } + + /// Reno /// + + inline void reno_init() { + reno_init_cwnd(3); + reno_init_sshtresh(); + } + + inline void reno_init_cwnd(size_t segments) + { + cb.cwnd = segments*SMSS(); + debug2(" Cwnd initilized: %u\n", cb.cwnd); + } + + inline void reno_init_sshtresh() + { cb.ssthresh = cb.SND.WND; } + + + inline void reno_increase_cwnd(uint16_t n) + { cb.cwnd += std::min(n, SMSS()); } + + inline void reno_deflate_cwnd(uint16_t n) + { cb.cwnd -= (n >= SMSS()) ? n-SMSS() : n; } + + inline void reduce_ssthresh() { + auto fs = flight_size(); + debug2(" FlightSize: %u\n", fs); + + auto two_seg = 2*(uint32_t)SMSS(); + + if(limited_tx_) + fs = (fs >= two_seg) ? fs - two_seg : 0; + + cb.ssthresh = std::max( (fs / 2), two_seg ); + debug2(" Slow start threshold reduced: %u\n", + cb.ssthresh); + } + + inline void fast_retransmit() { + debug(" Fast retransmit initiated.\n"); + // reduce sshtresh + reduce_ssthresh(); + // retransmit segment starting SND.UNA + retransmit(); + // inflate congestion window with the 3 packets we got dup ack on. + cb.cwnd = cb.ssthresh + 3*SMSS(); + fast_recovery = true; + } + + inline void finish_fast_recovery() { + reno_fpack_seen = false; + fast_recovery = false; + cb.cwnd = std::min(cb.ssthresh, std::max(flight_size(), (uint32_t)SMSS()) + SMSS()); + debug(" Finished Fast Recovery - Cwnd: %u\n", cb.cwnd); + } + + inline bool reno_full_ack(Seq ACK) + { return ACK - 1 > cb.recover; } + + /* + Generate a new ISS. + */ + TCP::Seq generate_iss(); + + + /// STATE HANDLING /// + /* + Set state. (used by substates) + */ + void set_state(State& state); + + /* + Transmit the send buffer. + */ + void transmit(); + + /* + Transmit the packet and hooks up retransmission. + */ + void transmit(TCP::Packet_ptr); + + /* + Creates a new outgoing packet with the current TCB values and options. + */ + TCP::Packet_ptr create_outgoing_packet(); + + /* + */ + inline TCP::Packet_ptr outgoing_packet() + { return create_outgoing_packet(); } + + + /// RETRANSMISSION /// + + /* + Retransmit the first packet in retransmission queue. + */ + void retransmit(); + + /* + Start retransmission timer. + */ + void rtx_start(); + + /* + Stop retransmission timer. + */ + void rtx_stop(); + + /* + Restart retransmission timer. + */ + inline void rtx_reset() { + rtx_stop(); + rtx_start(); + } + + /* + Number of retransmission attempts on the packet first in RT-queue + */ + size_t rto_attempt = 0; + + /* + Remove all packets acknowledge by ACK in retransmission queue + */ + void rtx_ack(Seq ack); + + /* + Delete retransmission queue + */ + void rtx_clear(); + + /* + When retransmission times out. + */ + void rtx_timeout(); + + + /* + Start the time wait timeout for 2*MSL + */ + void start_time_wait_timeout(); + + /* + Tell the host (TCP) to delete this connection. + */ + void signal_close(); + + + /// OPTIONS /// + /* + Maximum Segment Data Size + (Limit the size for outgoing packets) + */ + inline uint16_t MSDS() const { + return std::min(host_.MSS(), cb.SND.MSS) + sizeof(TCP::Header); + } + + /* + Parse and apply options. + */ + void parse_options(TCP::Packet_ptr); + + /* + Add an option. + */ + void add_option(TCP::Option::Kind, TCP::Packet_ptr); + + /* + Receive a TCP Packet. + */ + void segment_arrived(TCP::Packet_ptr); + + }; // < class TCP::Connection + + + /// USER INTERFACE - TCP /// + + /* + Constructor + */ + TCP(IPStack&); + + /* + Bind a new listener to a given Port. + */ + TCP::Connection& bind(Port port); + + /* + Active open a new connection to the given remote. + */ + Connection_ptr connect(Socket remote); + + /* + Active open a new connection to the given remote. + */ + inline auto connect(TCP::Address address, Port port = 80) { + return connect({address, port}); + } + + /* + Active open a new connection to the given remote. + */ + void connect(Socket remote, Connection::ConnectCallback); + + /* + Receive packet from network layer (IP). + */ + void bottom(net::Packet_ptr); + + /* + Delegate output to network layer + */ + inline void set_network_out(downstream del) { _network_layer_out = del; } + + /* + Compute the TCP checksum + */ static uint16_t checksum(const TCP::Packet_ptr); inline const auto& listeners() { return listeners_; } @@ -1077,97 +1827,123 @@ class TCP { inline const auto& connections() { return connections_; } /* - Number of open ports. + Number of open ports. */ - inline size_t openPorts() { return listeners_.size(); } - - /* - Number of active connections. - */ - inline size_t activeConnections() { return connections_.size(); } - - /* - Maximum Segment Lifetime - */ - inline auto MSL() const { return MAX_SEG_LIFETIME; } - - /* - Set Maximum Segment Lifetime - */ - inline void set_MSL(const std::chrono::milliseconds msl) { - MAX_SEG_LIFETIME = msl; - } - - /* - Maximum Buffer Size - */ - inline auto buffer_limit() const { return MAX_BUFFER_SIZE; } - - /* - Set Buffer limit - */ - inline void set_buffer_limit(size_t buffer_limit) { - MAX_BUFFER_SIZE = buffer_limit; - } - - /* - Show all connections for TCP as a string. - */ - std::string status() const; - - -private: - IPStack& inet_; - std::map listeners_; - std::map connections_; - - downstream _network_layer_out; - - /* - Settings - */ - TCP::Port current_ephemeral_ = 1024; - - std::chrono::milliseconds MAX_SEG_LIFETIME; - - /* - Current: limit by packet COUNT. - Connection buffer size in bytes = buffers * BUFFER_LIMIT * MTU. - */ - size_t MAX_BUFFER_SIZE = 10; - - /* - Transmit packet to network layer (IP). - */ - void transmit(TCP::Packet_ptr); - - /* - Generate a unique initial sequence number (ISS). - */ - TCP::Seq generate_iss(); - - /* - Returns a free port for outgoing connections. - */ - TCP::Port free_port(); - - /* - Packet is dropped. - */ - void drop(TCP::Packet_ptr); - - /* - Add a Connection. - */ - Connection_ptr add_connection(TCP::Port local_port, TCP::Socket remote); - - /* - Close and delete the connection. - */ - void close_connection(TCP::Connection&); - - -}; // < class TCP + inline size_t openPorts() { return listeners_.size(); } + + /* + Number of active connections. + */ + inline size_t activeConnections() { return connections_.size(); } + + /* + Maximum Segment Lifetime + */ + inline auto MSL() const { return MAX_SEG_LIFETIME; } + + /* + Set Maximum Segment Lifetime + */ + inline void set_MSL(const std::chrono::milliseconds msl) { + MAX_SEG_LIFETIME = msl; + } + + /* + Maximum Segment Size + [RFC 793] [RFC 879] [RFC 6691] + */ + inline constexpr uint16_t MSS() const { + return network().MDDS() - sizeof(TCP::Header); + } + + /* + Show all connections for TCP as a string. + */ + std::string to_string() const; + + inline std::string status() const + { return to_string(); } + + + + + private: + + IPStack& inet_; + std::map listeners_; + std::map connections_; + + downstream _network_layer_out; + + std::deque writeq; + + std::vector used_ports; + + /* + Settings + */ + TCP::Port current_ephemeral_ = 1024; + + std::chrono::milliseconds MAX_SEG_LIFETIME; + + /* + Transmit packet to network layer (IP). + */ + void transmit(TCP::Packet_ptr); + + /* + Generate a unique initial sequence number (ISS). + */ + static TCP::Seq generate_iss(); + + /* + Returns a free port for outgoing connections. + */ + TCP::Port next_free_port(); + + /* + Check if the port is in use either among "listeners" or "connections" + */ + bool port_in_use(const TCP::Port) const; + + /* + Packet is dropped. + */ + void drop(TCP::Packet_ptr); + + /* + Add a Connection. + */ + Connection_ptr add_connection(TCP::Port local_port, TCP::Socket remote); + + /* + Close and delete the connection. + */ + void close_connection(TCP::Connection&); + + /* + Process the write queue with the given amount of free packets. + */ + void process_writeq(size_t packets); + + /* + + */ + size_t send(Connection_ptr, const char* buffer, size_t n); + + /* + Force the TCP to process the it's queue with the current amount of available packets. + */ + inline void kick() { + process_writeq(inet_.transmit_queue_available()); + } + + inline IP4& network() const { + return inet_.ip_obj(); + } + + + }; // < class TCP }; // < namespace net diff --git a/api/net/tcp_connection_states.hpp b/api/net/tcp_connection_states.hpp index 67e314d43d..f0450d1d70 100644 --- a/api/net/tcp_connection_states.hpp +++ b/api/net/tcp_connection_states.hpp @@ -6,9 +6,9 @@ // 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. @@ -25,320 +25,365 @@ using Connection = TCP::Connection; using State = TCP::Connection::State; /* - CLOSED + CLOSED */ class Connection::Closed : public State { public: - inline static State& instance() { - static Closed instance; - return instance; - } + inline static State& instance() { + static Closed instance; + return instance; + } - virtual void open(Connection&, bool active = false) override; + virtual void open(Connection&, bool active = false) override; - virtual size_t send(Connection&, const char* buffer, size_t n, bool push) override; + virtual size_t send(Connection&, WriteBuffer&) override; - /* - PASSIVE: - <- Do nothing (Start listening). + /* + PASSIVE: + <- Do nothing (Start listening). - => Listen. + => Listen. - ACTIVE: - <- Send SYN. + ACTIVE: + <- Send SYN. - => SynSent - */ - virtual Result handle(Connection&, TCP::Packet_ptr in) override; + => SynSent + */ + virtual Result handle(Connection&, TCP::Packet_ptr in) override; - inline virtual std::string to_string() const override { - return "CLOSED"; - }; + inline virtual std::string to_string() const override { + return "CLOSED"; + }; private: - inline Closed() {}; + inline Closed() {}; }; /* - LISTEN + LISTEN */ class Connection::Listen : public State { public: - inline static State& instance() { - static Listen instance; - return instance; - } - virtual void open(Connection&, bool active = false) override; + inline static State& instance() { + static Listen instance; + return instance; + } + virtual void open(Connection&, bool active = false) override; - virtual size_t send(Connection&, const char* buffer, size_t n, bool push) override; + virtual size_t send(Connection&, WriteBuffer&) override; - virtual void close(Connection&) override; - /* - -> Receive SYN. + virtual void close(Connection&) override; + /* + -> Receive SYN. - <- Send SYN+ACK. + <- Send SYN+ACK. - => SynReceived. - */ - virtual Result handle(Connection&, TCP::Packet_ptr in) override; + => SynReceived. + */ + virtual Result handle(Connection&, TCP::Packet_ptr in) override; - inline virtual std::string to_string() const override { - return "LISTENING"; - }; + inline virtual std::string to_string() const override { + return "LISTENING"; + }; private: - inline Listen() {}; + inline Listen() {}; }; /* - SYN-SENT + SYN-SENT */ class Connection::SynSent : public State { public: - inline static State& instance() { - static SynSent instance; - return instance; - } + inline static State& instance() { + static SynSent instance; + return instance; + } - virtual size_t send(Connection&, const char* buffer, size_t n, bool push) override; + virtual size_t send(Connection&, WriteBuffer&) override; - virtual void close(Connection&) override; - /* - -> Receive SYN+ACK + virtual void close(Connection&) override; + /* + -> Receive SYN+ACK - <- Send ACK. + <- Send ACK. - => Established. - */ - virtual Result handle(Connection&, TCP::Packet_ptr in) override; + => Established. + */ + virtual Result handle(Connection&, TCP::Packet_ptr in) override; - inline virtual std::string to_string() const override { - return "SYN-SENT"; - }; + inline virtual std::string to_string() const override { + return "SYN-SENT"; + }; private: - inline SynSent() {}; + inline SynSent() {}; }; /* - SYN-RCV + SYN-RCV */ class Connection::SynReceived : public State { public: - inline static State& instance() { - static SynReceived instance; - return instance; - } + inline static State& instance() { + static SynReceived instance; + return instance; + } - virtual size_t send(Connection&, const char* buffer, size_t n, bool push) override; + virtual size_t send(Connection&, WriteBuffer&) override; - virtual void close(Connection&) override; + virtual void close(Connection&) override; - virtual void abort(Connection&) override; - /* - -> Receive ACK. + virtual void abort(Connection&) override; + /* + -> Receive ACK. - <- Do nothing (Connection is Established) + <- Do nothing (Connection is Established) - => Established. - */ - virtual Result handle(Connection&, TCP::Packet_ptr in) override; + => Established. + */ + virtual Result handle(Connection&, TCP::Packet_ptr in) override; - inline virtual std::string to_string() const override { - return "SYN-RCV"; - }; + inline virtual std::string to_string() const override { + return "SYN-RCV"; + }; private: - inline SynReceived() {}; + inline SynReceived() {}; }; /* - ESTABLISHED + ESTABLISHED */ class Connection::Established : public State { public: - inline static State& instance() { - static Established instance; - return instance; - } + inline static State& instance() { + static Established instance; + return instance; + } + + virtual size_t send(Connection&, WriteBuffer&) override; - virtual size_t send(Connection&, const char* buffer, size_t n, bool push) override; + virtual void receive(Connection&, ReadBuffer&) override; - virtual size_t receive(Connection&, char* buffer, size_t n) override; + virtual void close(Connection&) override; - virtual void close(Connection&) override; + virtual void abort(Connection&) override; - virtual void abort(Connection&) override; + virtual Result handle(Connection&, TCP::Packet_ptr in) override; - virtual Result handle(Connection&, TCP::Packet_ptr in) override; + inline virtual std::string to_string() const override { + return "ESTABLISHED"; + }; - inline virtual std::string to_string() const override { - return "ESTABLISHED"; - }; + inline virtual bool is_connected() const override { + return true; + } + + inline virtual bool is_writable() const override { + return true; + } + + inline virtual bool is_readable() const override { + return true; + } private: - inline Established() {}; + inline Established() {}; }; /* - FIN-WAIT-1 + FIN-WAIT-1 */ class Connection::FinWait1 : public State { public: - inline static State& instance() { - static FinWait1 instance; - return instance; - } + inline static State& instance() { + static FinWait1 instance; + return instance; + } + + virtual void receive(Connection&, ReadBuffer&) override; - virtual size_t receive(Connection&, char* buffer, size_t n) override; + virtual void close(Connection&) override; - virtual void close(Connection&) override; + virtual void abort(Connection&) override; - virtual void abort(Connection&) override; + /* + -> Receive ACK. - /* - -> Receive ACK. + => FinWait2. + */ + virtual Result handle(Connection&, TCP::Packet_ptr in) override; - => FinWait2. - */ - virtual Result handle(Connection&, TCP::Packet_ptr in) override; + inline virtual std::string to_string() const override { + return "FIN-WAIT-1"; + }; - inline virtual std::string to_string() const override { - return "FIN-WAIT-1"; - }; + inline virtual bool is_readable() const override { + return true; + } + + inline virtual bool is_closing() const override { + return true; + } private: - inline FinWait1() {}; + inline FinWait1() {}; }; /* - FIN-WAIT-2 + FIN-WAIT-2 */ class Connection::FinWait2 : public State { public: - inline static State& instance() { - static FinWait2 instance; - return instance; - } + inline static State& instance() { + static FinWait2 instance; + return instance; + } + + virtual void receive(Connection&, ReadBuffer&) override; + + virtual void close(Connection&) override; + + virtual void abort(Connection&) override; + /* + + */ + virtual Result handle(Connection&, TCP::Packet_ptr in) override; - virtual size_t receive(Connection&, char* buffer, size_t n) override; + inline virtual std::string to_string() const override { + return "FIN-WAIT-2"; + }; - virtual void close(Connection&) override; + inline virtual bool is_readable() const override { + return true; + } - virtual void abort(Connection&) override; - /* - - */ - virtual Result handle(Connection&, TCP::Packet_ptr in) override; + inline virtual bool is_closing() const override { + return true; + } - inline virtual std::string to_string() const override { - return "FIN-WAIT-2"; - }; private: - inline FinWait2() {}; + inline FinWait2() {}; }; /* - CLOSE-WAIT + CLOSE-WAIT */ class Connection::CloseWait : public State { public: - inline static State& instance() { - static CloseWait instance; - return instance; - } + inline static State& instance() { + static CloseWait instance; + return instance; + } - virtual size_t send(Connection&, const char* buffer, size_t n, bool push) override; + virtual size_t send(Connection&, WriteBuffer&) override; - virtual size_t receive(Connection&, char* buffer, size_t n) override; + virtual void receive(Connection&, ReadBuffer&) override; - virtual void close(Connection&) override; + virtual void close(Connection&) override; - virtual void abort(Connection&) override; - /* - -> Nothing I think... + virtual void abort(Connection&) override; + /* + -> Nothing I think... - <- Send FIN. - - => LastAck - */ - virtual Result handle(Connection&, TCP::Packet_ptr in) override; + <- Send FIN. - inline virtual std::string to_string() const override { - return "CLOSE-WAIT"; - }; + => LastAck + */ + virtual Result handle(Connection&, TCP::Packet_ptr in) override; + + inline virtual std::string to_string() const override { + return "CLOSE-WAIT"; + }; + + inline virtual bool is_writable() const override { + return true; + } private: - inline CloseWait() {}; + inline CloseWait() {}; }; /* - LAST-ACK + CLOSING */ -class Connection::LastAck : public State { +class Connection::Closing : public State { public: - inline static State& instance() { - static LastAck instance; - return instance; - } - /* - -> Receive ACK. + inline static State& instance() { + static Closing instance; + return instance; + } + /* + -> Receive ACK. - <- conn.onClose(); + => TimeWait (Guess this isnt needed, just start a Close-timer) + */ + virtual Result handle(Connection&, TCP::Packet_ptr in) override; - => Closed (Tell TCP to remove this connection) - */ - virtual Result handle(Connection&, TCP::Packet_ptr in) override; + inline virtual std::string to_string() const override { + return "CLOSING"; + }; - inline virtual std::string to_string() const override { - return "LAST-ACK"; - }; + inline virtual bool is_closing() const override { + return true; + } private: - inline LastAck() {}; + inline Closing() {}; }; /* - CLOSING + LAST-ACK */ -class Connection::Closing : public State { +class Connection::LastAck : public State { public: - inline static State& instance() { - static Closing instance; - return instance; - } - /* - -> Receive ACK. + inline static State& instance() { + static LastAck instance; + return instance; + } + /* + -> Receive ACK. - => TimeWait (Guess this isnt needed, just start a Close-timer) - */ - virtual Result handle(Connection&, TCP::Packet_ptr in) override; + <- conn.onClose(); - inline virtual std::string to_string() const override { - return "CLOSING"; - }; + => Closed (Tell TCP to remove this connection) + */ + virtual Result handle(Connection&, TCP::Packet_ptr in) override; + + inline virtual std::string to_string() const override { + return "LAST-ACK"; + }; + + inline virtual bool is_closing() const override { + return true; + } private: - inline Closing() {}; + inline LastAck() {}; }; /* - TIME-WAIT + TIME-WAIT */ class Connection::TimeWait : public State { public: - inline static State& instance() { - static TimeWait instance; - return instance; - } - /* - - */ - virtual Result handle(Connection&, TCP::Packet_ptr in) override; - - inline virtual std::string to_string() const override { - return "TIME-WAIT"; - }; + inline static State& instance() { + static TimeWait instance; + return instance; + } + /* + + */ + virtual Result handle(Connection&, TCP::Packet_ptr in) override; + + inline virtual std::string to_string() const override { + return "TIME-WAIT"; + }; + + inline virtual bool is_closing() const override { + return true; + } private: - inline TimeWait() {}; + inline TimeWait() {}; }; -#endif \ No newline at end of file +#endif diff --git a/api/net/util.hpp b/api/net/util.hpp index f5d3f7161a..621273bf2f 100644 --- a/api/net/util.hpp +++ b/api/net/util.hpp @@ -22,37 +22,37 @@ namespace net { -/* - * See P.49 of C programming - * Get "n" bits from integer "x", starting from position "p" - * e.g., getbits(x, 31, 8) -- highest byte - * getbits(x, 7, 8) -- lowest byte - */ + /* + * See P.49 of C programming + * Get "n" bits from integer "x", starting from position "p" + * e.g., getbits(x, 31, 8) -- highest byte + * getbits(x, 7, 8) -- lowest byte + */ #define getbits(x, p, n) (((x) >> ((p) + 1 - (n))) & ~(~0 << (n))) -inline uint16_t ntohs(uint16_t n) noexcept { - return __builtin_bswap16(n); -} + inline uint16_t ntohs(uint16_t n) noexcept { + return __builtin_bswap16(n); + } -inline uint16_t htons(uint16_t n) noexcept { - return __builtin_bswap16(n); -} + inline uint16_t htons(uint16_t n) noexcept { + return __builtin_bswap16(n); + } -inline uint32_t ntohl(uint32_t n) noexcept { - return __builtin_bswap32(n); -} + inline uint32_t ntohl(uint32_t n) noexcept { + return __builtin_bswap32(n); + } -inline uint32_t htonl(uint32_t n) noexcept { - return __builtin_bswap32(n); -} + inline uint32_t htonl(uint32_t n) noexcept { + return __builtin_bswap32(n); + } -inline uint64_t ntohll(uint64_t n) noexcept { - return __builtin_bswap64(n); -} + inline uint64_t ntohll(uint64_t n) noexcept { + return __builtin_bswap64(n); + } -inline uint64_t htonll(uint64_t n) noexcept { - return __builtin_bswap64(n); -} + inline uint64_t htonll(uint64_t n) noexcept { + return __builtin_bswap64(n); + } } //< namespace net diff --git a/api/serial b/api/serial new file mode 100644 index 0000000000..3edbaa54dc --- /dev/null +++ b/api/serial @@ -0,0 +1,23 @@ +// This file is a part of the IncludeOS unikernel - www.includeos.org +// +// Copyright 2015 Oslo and Akershus University College of Applied Sciences +// and Alfred Bratterud +// +// 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. + +#ifndef SERIAL_HEADER +#define SERIAL_HEADER + +#include "hw/serial.hpp" + +#endif diff --git a/api/sys/features.h b/api/sys/features.h index 8123fc2450..14d0bec648 100644 --- a/api/sys/features.h +++ b/api/sys/features.h @@ -6,9 +6,9 @@ // 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. @@ -17,25 +17,14 @@ #ifndef SYS_FEATURES_H #define SYS_FEATURES_H - - // Newlib needs this switch to enable clock_gettime etc. - // Also, we'll need posix timers sooner or later -#define _POSIX_TIMERS 1 - -#ifndef __GNUC_PREREQ -#define __GNUC_PREREQ(A, B) 0 /* Nei */ -#endif +// Newlib needs this switch to enable clock_gettime etc. +#define _POSIX_TIMERS 1 -/** Apparently this is necessary in order to build libc++. - @todo : It creates a warning when building os.a; - find another way to provide it to libc++. - */ -#ifndef __GNUC_PREREQ__ -#define __GNUC_PREREQ__(A, B) __GNUC_PREREQ(A, B) -#endif +// Required to pass CMake tests for libc++ +#define __GLIBC_PREREQ__(min, maj) 1 +#define __GLIBC_PREREQ(min, maj) 1 -#define __GLIBC_PREREQ__(A, B) 1 /* Jo. */ -#define __GLIBC_PREREQ(A, B) 1 /* Jo. */ +#include_next #endif diff --git a/api/sys/math.h b/api/sys/math.h index 1117eae483..fca5d9b13f 100644 --- a/api/sys/math.h +++ b/api/sys/math.h @@ -6,9 +6,9 @@ // 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. @@ -59,12 +59,10 @@ long double fdiml(long double, long double); long double fmal(long double, long double, long double); long double fmaxl(long double, long double); long double fminl(long double, long double); -long double hypotl(long double, long double); - long double expl(long double); long double ldexpl(long double, long double); -long double sqrtl(long double); + long double ilogbl(long double); long double lgammal(long double); long double llroundl(long double); diff --git a/api/sys/time.h b/api/sys/time.h index fc24390a5a..ea23f605ad 100644 --- a/api/sys/time.h +++ b/api/sys/time.h @@ -6,9 +6,9 @@ // 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. @@ -18,13 +18,12 @@ #include "features.h" #include_next #include_next + #ifndef SYS_TIME_H #define SYS_TIME_H - // We might possibly need additions here. #define CLOCK_MONOTONIC 4 #endif - diff --git a/api/sys/wchar.h b/api/sys/wchar.h index 24d41af9ff..0e3b142168 100644 --- a/api/sys/wchar.h +++ b/api/sys/wchar.h @@ -6,9 +6,9 @@ // 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. @@ -25,7 +25,7 @@ extern "C" { #endif /** @WARNING Loses precision **/ -static inline long double wcstold (const wchar_t* str, wchar_t** endptr) { +inline long double wcstold (const wchar_t* str, wchar_t** endptr) { return wcstod(str,endptr); } diff --git a/api/term b/api/term new file mode 100644 index 0000000000..16a25ddd30 --- /dev/null +++ b/api/term @@ -0,0 +1,23 @@ +// This file is a part of the IncludeOS unikernel - www.includeos.org +// +// Copyright 2015 Oslo and Akershus University College of Applied Sciences +// and Alfred Bratterud +// +// 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. + +#ifndef TERM_HEADER +#define TERM_HEADER + +#include "kernel/terminal.hpp" + +#endif diff --git a/api/utility/async.hpp b/api/utility/async.hpp new file mode 100644 index 0000000000..8fcc0a25ad --- /dev/null +++ b/api/utility/async.hpp @@ -0,0 +1,38 @@ +#pragma once +#ifndef UTILITY_ASYNC_HPP +#define UTILITY_ASYNC_HPP + +#include +#include +#include + +class Async +{ +public: + static const size_t PAYLOAD_SIZE = 64000; + + typedef net::TCP::Connection_ptr Connection; + typedef fs::Disk_ptr Disk; + typedef fs::FileSystem::Dirent Dirent; + + typedef std::function next_func; + typedef std::function on_after_func; + typedef std::function on_write_func; + + static void upload_file( + Disk, + const Dirent&, + Connection, + on_after_func, + size_t = PAYLOAD_SIZE); + + static void disk_transfer( + Disk, + const Dirent&, + on_write_func, + on_after_func, + size_t = PAYLOAD_SIZE); + +}; + +#endif diff --git a/api/utility/async_loop.hpp b/api/utility/async_loop.hpp index c4c456a360..87f3f1676e 100644 --- a/api/utility/async_loop.hpp +++ b/api/utility/async_loop.hpp @@ -6,27 +6,27 @@ typedef std::shared_ptr next_ptr_t; inline void async_loop( - std::function func, - std::function on_done) + std::function func, + std::function on_done) { // store next function on heap auto next = std::make_shared (); // loop: *next = - [next] (bool done) - { - // check we are done, and if so, - // execute the callback function and return - if (done) + [next] (bool done) { - on_done(); - return; + // check we are done, and if so, + // execute the callback function and return + if (done) + { + on_done(); + return; + } + // otherwise, + // execute one iteration of the loop + func(next); } - // otherwise, - // execute one iteration of the loop - func(next); - } // start the process next(false); } diff --git a/api/utility/delegate.hpp b/api/utility/delegate.hpp index 27c04c64cd..5956da5491 100644 --- a/api/utility/delegate.hpp +++ b/api/utility/delegate.hpp @@ -7,7 +7,7 @@ Licence: Assumed to be public domain. ...It's just awesome how people make great stuff and just post it - */ +*/ #ifndef OSABI_DELEGATE_HPP #define OSABI_DELEGATE_HPP @@ -25,7 +25,7 @@ class delegate using stub_ptr_type = R (*)(void*, A&&...); delegate(void* const o, stub_ptr_type const m) noexcept : - object_ptr_(o), + object_ptr_(o), stub_ptr_(m) { } @@ -42,16 +42,16 @@ class delegate delegate(::std::nullptr_t const) noexcept : delegate() { } template {}>::type> - explicit delegate(C const* const o) noexcept : - object_ptr_(const_cast(o)) + typename ::std::enable_if< ::std::is_class{}>::type> + explicit delegate(C const* const o) noexcept : + object_ptr_(const_cast(o)) { } template {}>::type> - explicit delegate(C const& o) noexcept : - object_ptr_(const_cast(&o)) + typename ::std::enable_if< ::std::is_class{}>::type> + explicit delegate(C const& o) noexcept : + object_ptr_(const_cast(&o)) { } @@ -84,11 +84,11 @@ class delegate typename = typename ::std::enable_if< !::std::is_same::type>{} >::type - > - delegate(T&& f) : + > + delegate(T&& f) : store_(operator new(sizeof(typename ::std::decay::type)), - functor_deleter::type>), - store_size_(sizeof(typename ::std::decay::type)) + functor_deleter::type>), + store_size_(sizeof(typename ::std::decay::type)) { using functor_type = typename ::std::decay::type; @@ -122,22 +122,22 @@ class delegate typename = typename ::std::enable_if< !::std::is_same::type>{} >::type - > - delegate& operator=(T&& f) + > + delegate& operator=(T&& f) { using functor_type = typename ::std::decay::type; if ((sizeof(functor_type) > store_size_) || !store_.unique()) - { - store_.reset(operator new(sizeof(functor_type)), - functor_deleter); + { + store_.reset(operator new(sizeof(functor_type)), + functor_deleter); - store_size_ = sizeof(functor_type); - } + store_size_ = sizeof(functor_type); + } else - { - deleter_(store_.get()); - } + { + deleter_(store_.get()); + } new (store_.get()) functor_type(::std::forward(f)); @@ -201,14 +201,14 @@ class delegate template static delegate from(C* const object_ptr, - R (C::* const method_ptr)(A...)) + R (C::* const method_ptr)(A...)) { return member_pair(object_ptr, method_ptr); } template static delegate from(C const* const object_ptr, - R (C::* const method_ptr)(A...) const) + R (C::* const method_ptr)(A...) const) { return const_member_pair(object_ptr, method_ptr); } @@ -221,7 +221,7 @@ class delegate template static delegate from(C const& object, - R (C::* const method_ptr)(A...) const) + R (C::* const method_ptr)(A...) const) { return const_member_pair(&object, method_ptr); } @@ -262,7 +262,7 @@ class delegate R operator()(A... args) const { -// assert(stub_ptr); + // assert(stub_ptr); return stub_ptr_(object_ptr_, ::std::forward(args)...); } @@ -303,14 +303,14 @@ class delegate static R method_stub(void* const object_ptr, A&&... args) { return (static_cast(object_ptr)->*method_ptr)( - ::std::forward(args)...); + ::std::forward(args)...); } template static R const_method_stub(void* const object_ptr, A&&... args) { return (static_cast(object_ptr)->*method_ptr)( - ::std::forward(args)...); + ::std::forward(args)...); } template @@ -318,7 +318,7 @@ class delegate template struct is_member_pair< ::std::pair > : std::true_type + R (C::* const)(A...)> > : std::true_type { }; @@ -327,16 +327,16 @@ class delegate template struct is_const_member_pair< ::std::pair > : std::true_type + R (C::* const)(A...) const> > : std::true_type { }; template static typename ::std::enable_if< !(is_member_pair{} || - is_const_member_pair{}), + is_const_member_pair{}), R - >::type + >::type functor_stub(void* const object_ptr, A&&... args) { return (*static_cast(object_ptr))(::std::forward(args)...); @@ -345,13 +345,13 @@ class delegate template static typename ::std::enable_if< is_member_pair{} || - is_const_member_pair{}, + is_const_member_pair{}, R - >::type - functor_stub(void* const object_ptr, A&&... args) + >::type + functor_stub(void* const object_ptr, A&&... args) { return (static_cast(object_ptr)->first->* - static_cast(object_ptr)->second)(::std::forward(args)...); + static_cast(object_ptr)->second)(::std::forward(args)...); } }; @@ -365,7 +365,7 @@ namespace std auto const seed(hash()(d.object_ptr_)); return hash::stub_ptr_type>()( - d.stub_ptr_) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + d.stub_ptr_) + 0x9e3779b9 + (seed << 6) + (seed >> 2); } }; } diff --git a/api/utility/membitmap.hpp b/api/utility/membitmap.hpp index bf8606ced3..e08d4c0dca 100644 --- a/api/utility/membitmap.hpp +++ b/api/utility/membitmap.hpp @@ -23,83 +23,77 @@ * In-memory bitmap implementation * * -**/ + **/ -namespace fs +class MemBitmap { - class MemBitmap +public: + typedef uint32_t word; + static const word WORD_MAX = UINT32_MAX; + typedef uint32_t index_t; + static const int CHUNK_SIZE = sizeof(word) * 8; + + MemBitmap() = default; + MemBitmap(void* location, index_t chunks) + : size_(chunks) + { + _data = reinterpret_cast(location); + } + + // returns the boolean value of the bit located at @n + bool operator[] (index_t n) const + { + return get(n); + } + bool get(index_t b) const + { + return _data[windex(b)] & (1 << woffset(b)); + } + // return the bit-index of the first clear bit + index_t first_free() const + { + // each word + for (index_t i = 0; i < size(); i++) + if (_data[i] != WORD_MAX) { + // each bit + for (index_t b = 0; b < CHUNK_SIZE; b++) + if (!(_data[i] & (1 << b))) { + return i * CHUNK_SIZE + b; + } + } // chunk + return -1; + } // first_free() + + void zero_all() { - public: - typedef uint64_t word; - static const word WORD_MAX = UINT64_MAX; - typedef uint32_t index_t; - static const int CHUNK_SIZE = sizeof(word) * 8; - - MemBitmap() = default; - MemBitmap(void* location, index_t chunks) - { - _data = reinterpret_cast(location); - _size = chunks; - } - - // returns the boolean value of the bit located at @n - inline bool operator[] (index_t n) const - { - return get(n); - } - inline bool get(index_t b) const - { - return _data[windex(b)] & (1 << woffset(b)); - } - // return the bit-index of the first clear bit - index_t first_free() const - { - // each word - for (index_t i = 0; i < _size; i++) - if (_data[i] != WORD_MAX) - { - // each bit - for (index_t b = 0; b < CHUNK_SIZE; b++) - if (!(_data[i] & (1 << b))) - { - return i * CHUNK_SIZE + b; - } - } // chunk - return -1; - } // first_free() - - void zero_all() - { - streamset32(_data, 0, size()); - } - void set(index_t b) - { - _data[windex(b)] |= 1 << (woffset(b)); - } - void clear(index_t b) - { - _data[windex(b)] &= ~(1 << (woffset(b))); - } - void flip(index_t b) - { - _data[windex(b)] ^= 1 << (woffset(b)); - } - - inline char* data() const - { - return (char*) _data; - } - inline size_t size() const - { - return _size * CHUNK_SIZE; - } - - private: - inline index_t windex (index_t b) const { return b / CHUNK_SIZE; } - inline index_t woffset(index_t b) const { return b % CHUNK_SIZE; } - - word* _data{nullptr}; - index_t _size{}; - }; + streamset32(_data, 0, size()); + } + void set(index_t b) + { + _data[windex(b)] |= 1 << woffset(b); + } + void clear(index_t b) + { + _data[windex(b)] &= ~(1 << woffset(b)); + } + void flip(index_t b) + { + _data[windex(b)] ^= 1 << woffset(b); + } + + char* data() const noexcept + { + return (char*) _data; + } + size_t size() const noexcept + { + return size_ * CHUNK_SIZE; + } + +private: + index_t windex (index_t b) const { return b / CHUNK_SIZE; } + index_t woffset(index_t b) const { return b & (CHUNK_SIZE-1); } -} + word* _data {nullptr}; + index_t size_ {}; +}; diff --git a/api/utility/ringbuffer.hpp b/api/utility/ringbuffer.hpp index 88bd7f9f38..01f377e757 100644 --- a/api/utility/ringbuffer.hpp +++ b/api/utility/ringbuffer.hpp @@ -24,110 +24,110 @@ namespace includeOS { - class RingBuffer - { - enum error_t - { - E_OK = 0, - E_NO_SPACE = -1; - E_WRITE_FAILED = -2; - }; - - - RingBuffer(int size) - { - this->size = size + 1; - this->start = 0; - this->end = 0; - this->buffer = new char[this->size]; - } - ~RingBuffer() - { - if (this->buffer) - delete[] this->buffer; - } - - int write(char* data, int length) - { - if (available_data() == 0) - { - this->start = this->end = 0; - } - - if (length > available_space()) - { - return E_NO_SPACE; - } - - void* result = memcpy(ends_at(), data, length); - if (result == nullptr) - { - return E_WRITE_FAILED; - } - - // commit write - this->end = (this->end + length) % this->size; - // return length written - return length; - } - - int read(char* dest, int length) - { - check_debug(amount <= RingBuffer_available_data(buffer), - "Not enough in the buffer: has %d, needs %d", - RingBuffer_available_data(buffer), amount); + class RingBuffer + { + enum error_t + { + E_OK = 0, + E_NO_SPACE = -1; + E_WRITE_FAILED = -2; + }; + + + RingBuffer(int size) + { + this->size = size + 1; + this->start = 0; + this->end = 0; + this->buffer = new char[this->size]; + } + ~RingBuffer() + { + if (this->buffer) + delete[] this->buffer; + } + + int write(char* data, int length) + { + if (available_data() == 0) + { + this->start = this->end = 0; + } + + if (length > available_space()) + { + return E_NO_SPACE; + } + + void* result = memcpy(ends_at(), data, length); + if (result == nullptr) + { + return E_WRITE_FAILED; + } + + // commit write + this->end = (this->end + length) % this->size; + // return length written + return length; + } + + int read(char* dest, int length) + { + check_debug(amount <= RingBuffer_available_data(buffer), + "Not enough in the buffer: has %d, needs %d", + RingBuffer_available_data(buffer), amount); - void *result = memcpy(target, RingBuffer_starts_at(buffer), amount); - check(result != NULL, "Failed to write buffer into data."); - - // commit read - this->start = (this->start + length) % this->size; - - if (this->end == this->start) - { - this->start = this->end = 0; - } - - return length; - } - - #define RingBuffer_available_data(B) (((B)->end + 1) % (B)->length - (B)->start - 1) - #define RingBuffer_available_space(B) ((B)->length - (B)->end - 1) - - int available_data() const - { - return (this->end + 1) % this->size - this->start - 1; - } - int available_space() const - { - return this-> - } - - const char* starts_at() const - { - return this->buffer + this->end; - } - const char* ends_at() const - { - return this->buffer + this->end; - } - - bool full() const - { - return available_data() - this->size == 0; - } - bool empty() const - { - return available_data() == 0; - } - - - private: - int size; - int start; - int end; - char* buffer; - }; + void *result = memcpy(target, RingBuffer_starts_at(buffer), amount); + check(result != NULL, "Failed to write buffer into data."); + + // commit read + this->start = (this->start + length) % this->size; + + if (this->end == this->start) + { + this->start = this->end = 0; + } + + return length; + } + +#define RingBuffer_available_data(B) (((B)->end + 1) % (B)->length - (B)->start - 1) +#define RingBuffer_available_space(B) ((B)->length - (B)->end - 1) + + int available_data() const + { + return (this->end + 1) % this->size - this->start - 1; + } + int available_space() const + { + return this-> + } + + const char* starts_at() const + { + return this->buffer + this->end; + } + const char* ends_at() const + { + return this->buffer + this->end; + } + + bool full() const + { + return available_data() - this->size == 0; + } + bool empty() const + { + return available_data() == 0; + } + + + private: + int size; + int start; + int end; + char* buffer; + }; } #endif diff --git a/api/utility/signal.hpp b/api/utility/signal.hpp index c15d4cc269..f18de4b26b 100644 --- a/api/utility/signal.hpp +++ b/api/utility/signal.hpp @@ -24,42 +24,42 @@ template class signal { public: - //! \brief Callable type of the signal handlers - using handler = std::function; + //! \brief Callable type of the signal handlers + using handler = std::function; - //! \brief Default constructor - explicit signal() = default; + //! \brief Default constructor + explicit signal() = default; - //! \brief Default destructor - ~signal() noexcept = default; + //! \brief Default destructor + ~signal() noexcept = default; - //! \brief Default move constructor - explicit signal(signal&&) noexcept = default; + //! \brief Default move constructor + explicit signal(signal&&) noexcept = default; - //! \brief Default assignment operator - signal& operator=(signal&&) = default; + //! \brief Default assignment operator + signal& operator=(signal&&) = default; - //! \brief Connect a callable object to this signal - void connect(handler&& fn) { - funcs.emplace_back(std::forward(fn)); - } - - //! \brief Emit this signal by executing all connected callable objects - template - void emit(Args&&... args) { - for(auto& fn : funcs) - fn(std::forward(args)...); - } - + //! \brief Connect a callable object to this signal + void connect(handler&& fn) { + funcs.emplace_back(std::forward(fn)); + } + + //! \brief Emit this signal by executing all connected callable objects + template + void emit(Args&&... args) { + for(auto& fn : funcs) + fn(std::forward(args)...); + } + private: - // Set of callable objects registered to be called on demand - std::vector funcs; + // Set of callable objects registered to be called on demand + std::vector funcs; - // Avoid copying - signal(signal const&) = delete; + // Avoid copying + signal(signal const&) = delete; - // Avoid assignment - signal& operator=(signal const&) = delete; + // Avoid assignment + signal& operator=(signal const&) = delete; }; //< class signal #endif //< UTILITY_SIGNAL_HPP diff --git a/api/fs/vbr.hpp b/api/vga similarity index 85% rename from api/fs/vbr.hpp rename to api/vga index bef8b24129..3b141fc65c 100644 --- a/api/fs/vbr.hpp +++ b/api/vga @@ -1,3 +1,4 @@ +// -*-C++-*- // This file is a part of the IncludeOS unikernel - www.includeos.org // // Copyright 2015 Oslo and Akershus University College of Applied Sciences @@ -16,15 +17,9 @@ // limitations under the License. #pragma once -#ifndef FS_VBR_HPP -#define FS_VBR_HPP +#ifndef VGA_HEADER +#define VGA_HEADER -namespace fs { +#include "kernel/vga.hpp" -class VBR { - -}; //< class VBR - -} //< namespace fs - -#endif //< FS_VBR_HPP +#endif diff --git a/api/virtio/block.hpp b/api/virtio/block.hpp index db8167b0d1..1f5d7f227a 100644 --- a/api/virtio/block.hpp +++ b/api/virtio/block.hpp @@ -6,9 +6,9 @@ // 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. @@ -23,43 +23,44 @@ #include #include #include "virtio.hpp" -//#include +#include /** Virtio-net device driver. */ class VirtioBlk : public Virtio, public hw::IDiskDevice { public: static constexpr size_t SECTOR_SIZE = 512; - + /** Human readable name. */ - virtual const char* name() const noexcept override - { + virtual const char* name() const noexcept override { return "VirtioBlk"; } - + // returns the optimal block size for this device - virtual block_t block_size() const noexcept override - { + virtual block_t block_size() const noexcept override { return SECTOR_SIZE; // some multiple of sector size } + // read @blk from disk, call func with buffer when done virtual void read(block_t blk, on_read_func func) override; - - virtual void read(block_t, block_t, on_read_func cb) override - { - cb(buffer_t()); + // read @blk + @cnt from disk, call func with buffer when done + virtual void read(block_t blk, size_t cnt, on_read_func cb) override; + + // unsupported sync reads + virtual buffer_t read_sync(block_t) override { + return buffer_t(); } - - virtual buffer_t read_sync(block_t blk) override; - - virtual block_t size() const noexcept override - { + virtual buffer_t read_sync(block_t, size_t) override { + return buffer_t(); + } + + virtual block_t size() const noexcept override { return config.capacity; } - + /** Constructor. @param pcidev an initialized PCI device. */ VirtioBlk(hw::PCI_Device& pcidev); - + private: struct virtio_blk_geometry_t { @@ -67,7 +68,7 @@ class VirtioBlk : public Virtio, public hw::IDiskDevice uint8_t heds; uint8_t sect; } __attribute__((packed)); - + struct virtio_blk_config_t { uint64_t capacity; @@ -78,54 +79,73 @@ class VirtioBlk : public Virtio, public hw::IDiskDevice uint8_t physical_block_exp; // Exponent for physical block per logical block uint8_t alignment_offset; // Alignment offset in logical blocks uint16_t min_io_size; // Minimum I/O size without performance penalty in logical blocks - uint32_t opt_io_size; // Optimal sustained I/O size in logical blocks - } __attribute__((packed)); - + uint32_t opt_io_size; // Optimal sustained I/O size in logical blocks + }; + struct scsi_header_t { uint32_t type; uint32_t ioprio; uint64_t sector; - /// SCSI /// - //char* cmd = nullptr; - } __attribute__((packed)); - struct blk_data_t + }; + struct blk_io_t { - uint8_t sector[512]; - uint32_t stuff1; - on_read_func* handler; - uint32_t stuff2; - uint8_t status; - } __attribute__((packed)); - + uint8_t sector[512]; + }; + struct blk_resp_t + { + uint8_t status; + bool partial; + on_read_func handler; + }; + struct request_t { scsi_header_t hdr; - blk_data_t data; - } __attribute__((packed)); - + blk_io_t io; + blk_resp_t resp; + + request_t(uint64_t blk, bool, on_read_func cb); + }; + /** Get virtio PCI config. @see Virtio::get_config.*/ void get_config(); - - /** Service the RX Queue. + + /** Service the RX Queue. Push incoming data up to linklayer, dequeue RX buffers. */ void service_RX(); - - /** Service the TX Queue - Dequeue used TX buffers. @note: This function does not take any + + /** Service the TX Queue + Dequeue used TX buffers. @note: This function does not take any responsibility for memory management. */ void service_TX(); - - /** Handle device IRQ. - + + /** Handle device IRQ. + Will look for config. changes and service RX/TX queues as necessary.*/ void irq_handler(); - Virtio::Queue req; + // need at least 3 tokens free to ship a request + inline bool free_space() const noexcept + { return req.num_free() >= 3; } + + // need many free tokens free to efficiently ship requests + inline bool lots_free_space() const noexcept + { return req.num_free() >= 32; } + // add one request to queue and kick + void shipit(request_t*); + + void handle(request_t*); + + Virtio::Queue req; + // configuration as read from paravirtual PCI device virtio_blk_config_t config; - uint16_t request_counter; + + // queue waiting for space in vring + std::deque jobs; + size_t inflight; }; #endif diff --git a/api/virtio/console.hpp b/api/virtio/console.hpp index 89b622792d..951789bd9b 100644 --- a/api/virtio/console.hpp +++ b/api/virtio/console.hpp @@ -41,7 +41,7 @@ * placed in the receive queue for incoming data and outgoing * characters are placed in the transmit queue. * -**/ + **/ class VirtioCon : public Virtio { @@ -98,7 +98,7 @@ class VirtioCon : public Virtio /** * Handle device IRQ. * Will look for config. changes and service RX/TX queues as necessary. - **/ + **/ void irq_handler(); Virtio::Queue rx; // 0 diff --git a/api/virtio/virtio.hpp b/api/virtio/virtio.hpp index 1d4fd0ba40..9b7a30a143 100644 --- a/api/virtio/virtio.hpp +++ b/api/virtio/virtio.hpp @@ -6,35 +6,37 @@ // 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. -/** - @note This virtio implementation was very much inspired by - SanOS, (C) Michael Ringgaard. All due respect. - - STANDARD: - - We're aiming to become standards compilant according to this one: - - Virtio 1.0, OASIS Committee Specification Draft 03 - (http://docs.oasis-open.org/virtio/virtio/v1.0/virtio-v1.0.html) - - In the following abbreviated to Virtio 1.03 or Virtio std. +/** + @note This virtio implementation was very much inspired by + SanOS, (C) Michael Ringgaard. All due respect. + + STANDARD: + + We're aiming to become standards compilant according to this one: + + Virtio 1.0, OASIS Committee Specification Draft 03 + (http://docs.oasis-open.org/virtio/virtio/v1.0/virtio-v1.0.html) + + In the following abbreviated to Virtio 1.03 or Virtio std. */ #pragma once #ifndef VIRTIO_VIRTIO_HPP #define VIRTIO_VIRTIO_HPP #include "../hw/pci_device.hpp" -#include +#include #include +#include +#include #define PAGE_SIZE 4096 @@ -61,182 +63,216 @@ #define VIRTIO_CONFIG_S_DRIVER_OK 4 #define VIRTIO_CONFIG_S_FAILED 0x80 -/** A simple scatter-gather list used for Queue::enqueue. - ( From sanos, virtio.h - probably Linux originally) - */ -struct scatterlist { - void* data; - int size; -}; //#include class Virtio { -public: + +public: + using data_handler_t = delegate; + + /** A wrapper for buffers to be passed in to the Queue */ + class Token { + + public: + // "Direction" of tokens + using span = std::pair; //gsl::span; + using size_type = size_t;//span::size_type; + enum Direction { IN, OUT }; + inline Token(span buf, Direction d) : + data_{ buf.first }, size_{ buf.second }, dir_{ d } + {} + + inline auto data() { return data_; } + inline auto size() { return size_; } + inline auto direction() { return dir_; } + + + private: + uint8_t* data_; + size_type size_; + Direction dir_; + }; + // http://docs.oasis-open.org/virtio/virtio/v1.0/csprd01/virtio-v1.0-csprd01.html#x1-860005 // Virtio device types enum virtiotype_t - { - RESERVED = 0, - NIC, - BLOCK, - CONSOLE, - ENTROPY, - BALLOON, - IO_MEM, - RP_MSG, - SCSI_HOST, - T9P, - WLAN, - RP_SERIAL, - CAIF - }; - + { + RESERVED = 0, + NIC, + BLOCK, + CONSOLE, + ENTROPY, + BALLOON, + IO_MEM, + RP_MSG, + SCSI_HOST, + T9P, + WLAN, + RP_SERIAL, + CAIF + }; + /** Virtio Queue class. */ class Queue { + + private: + /** @note Using typedefs in order to keep the standard notation. */ - typedef uint64_t le64; - typedef uint32_t le32; - typedef uint16_t le16; - typedef uint16_t u16; - typedef uint8_t u8; + using le64 = uint64_t; + using le32 = uint32_t; + using le16 = uint16_t; + using u16 = uint16_t; + using u8 = uint8_t; - /** Virtio Ring Descriptor. Virtio std. §2.4.5 */ - struct virtq_desc { - /* Address (guest-physical). */ - le64 addr; - /* Length. */ - le32 len; - - /* This marks a buffer as continuing via the next field. */ - #define VIRTQ_DESC_F_NEXT 1 - /* This marks a buffer as device write-only (otherwise device read-only). */ - #define VIRTQ_DESC_F_WRITE 2 - /* This means the buffer contains a list of buffer descriptors. */ - #define VIRTQ_DESC_F_INDIRECT 4 - /* The flags as indicated above. */ - le16 flags; - /* Next field if flags & NEXT */ - le16 next; + struct virtq_desc { + /* Address (guest-physical). */ + le64 addr; + /* Length. */ + le32 len; + + /* This marks a buffer as continuing via the next field. */ +#define VIRTQ_DESC_F_NEXT 1 + /* This marks a buffer as device write-only (otherwise device read-only). */ +#define VIRTQ_DESC_F_WRITE 2 + /* This means the buffer contains a list of buffer descriptors. */ +#define VIRTQ_DESC_F_INDIRECT 4 + /* The flags as indicated above. */ + le16 flags; + /* Next field if flags & NEXT */ + le16 next; }; - - + + /** Virtio Available ring. Virtio std. §2.4.6 */ - struct virtq_avail { -#define VIRTQ_AVAIL_F_NO_INTERRUPT 1 - le16 flags; - le16 idx; - le16 ring[/* Queue Size */]; - /*le16 used_event; Only if VIRTIO_F_EVENT_IDX */ + struct virtq_avail { +#define VIRTQ_AVAIL_F_NO_INTERRUPT 1 + le16 flags; + le16 idx; + le16 ring[/* Queue Size */]; + /*le16 used_event; Only if VIRTIO_F_EVENT_IDX */ }; - - + + /** Virtio Used ring elements. Virtio std. §2.4.8 */ - struct virtq_used_elem { - /* le32 is used here for ids for padding reasons. */ - /* Index of start of used descriptor chain. */ - le32 id; - /* Total length of the descriptor chain which was used (written to) */ - le32 len; + struct virtq_used_elem { + /* le32 is used here for ids for padding reasons. */ + /* Index of start of used descriptor chain. */ + le32 id; + /* Total length of the descriptor chain which was used (written to) */ + le32 len; }; - + /** Virtio Used ring. Virtio std. §2.4.8 */ - struct virtq_used { -#define VIRTQ_USED_F_NO_NOTIFY 1 - le16 flags; - le16 idx; - struct virtq_used_elem ring[ /* Queue Size */]; - /*le16 avail_event; Only if VIRTIO_F_EVENT_IDX */ - }; - - + struct virtq_used { +#define VIRTQ_USED_F_NO_NOTIFY 1 + le16 flags; + le16 idx; + struct virtq_used_elem ring[ /* Queue Size */]; + /*le16 avail_event; Only if VIRTIO_F_EVENT_IDX */ + }; + + /** Virtqueue. Virtio std. §2.4.2 */ - struct virtq { - // The actual descriptors (16 bytes each) - virtq_desc* desc;// [ /* Queue Size*/ ]; - - // A ring of available descriptor heads with free-running index. - virtq_avail* avail; - - // Padding to the next PAGE_SIZE boundary. - u8* pad; //[ /* Padding */ ]; - - // A ring of used descriptor heads with free-running index. - virtq_used* used; + struct virtq { + // The actual descriptors (i.e. tokens) (16 bytes each) + virtq_desc* desc;// [ /* Queue Size*/ ]; + + // A ring of available descriptor heads with free-running index. + virtq_avail* avail; + + // Padding to the next PAGE_SIZE boundary. + u8* pad; //[ /* Padding */ ]; + + // A ring of used descriptor heads with free-running index. + virtq_used* used; }; - + /** Virtque size calculation. Virtio std. §2.4.2 */ static inline unsigned virtq_size(unsigned int qsz); - + // The size as read from the PCI device uint16_t _size; - + // Actual size in bytes - virtq_size(size) - uint32_t _size_bytes; - + uint32_t _size_bytes; + // The actual queue struct virtq _queue; - + uint16_t _iobase = 0; // Device PCI location - uint16_t _num_free = 0; // Number of free descriptors - uint16_t _free_head = 0; // First available descriptor + uint16_t _free_head = 0; // First available descriptor (_queue.desc[_free_head]) uint16_t _num_added = 0; // Entries to be added to _queue.avail->idx - uint16_t _last_used_idx = 0; // Last entry inserted by device + uint16_t _desc_in_flight = 0; // Entries in _queue_desc currently in use + uint16_t _last_used_idx = 0; // Last known value of _queue.used->idx uint16_t _pci_index = 0; // Queue nr. - //void **_data; - - + + delegate on_exit_to_physical_ {}; + /** Handler for data coming in on virtq.used. */ - delegate _data_handler; - + data_handler_t _data_handler; + /** Initialize the queue buffer */ void init_queue(int size, void* buf); public: + /** + Update the available index */ + inline void update_avail_idx () + { + // Std. §3.2.1 pt. 4 + asm volatile("mfence" ::: "memory"); + _queue.avail->idx += _num_added; + _num_added = 0; + } + /** Kick hypervisor. - + Will notify the host (Qemu/Virtualbox etc.) about pending data */ void kick(); /** Constructor. @param size shuld be fetched from PCI device. */ Queue(uint16_t size, uint16_t q_index, uint16_t iobase); - + /** Get the queue descriptor. To be written to the Virtio device. */ virtq_desc* queue_desc() const { return _queue.desc; } - - /** Push data tokens onto the queue. - @param sg : A scatterlist of tokens - @param out : The number of outbound tokens (device-readable - TX) - @param in : The number of tokens to be inbound (device-writable RX) + + /** Push data tokens onto the queue. + @param buffers : A span of tokens */ - int enqueue(scatterlist sg[], uint32_t out, uint32_t in, void*); - - void enqueue(void* out, uint32_t out_len, void* in, uint32_t in_len); - void* dequeue(uint32_t& len); - - /** Dequeue a received packet. From SanOS */ - uint8_t* dequeue(uint32_t* len); - + int enqueue(gsl::span buffers); + + /** Dequeue a received packet */ + Token dequeue(); + std::vector dequeue_chain(); + void disable_interrupts(); void enable_interrupts(); - - void set_data_handler(delegate dataHandler); - + + void set_data_handler(data_handler_t dataHandler); + /** Release token. @param head : the token ID to release*/ void release(uint32_t head); - - /** Get number of free tokens in Queue */ - inline uint16_t num_free(){ return _num_free; } - /** Get number of new incoming buffers */ - inline uint16_t new_incoming() + /** Get number of new incoming buffers, i.e. the increase in + queue_used_->idx since we last checked. An increase means the device + has inserted tokens into the used ring.*/ + uint16_t new_incoming() const noexcept { return _queue.used->idx - _last_used_idx; } - inline uint16_t num_avail() + /** Get number of used buffers */ + uint16_t num_used() const noexcept { return _queue.avail->idx - _queue.used->idx; } - + + /** Get number of free tokens in Queue */ + uint16_t num_free() const noexcept + { + //Expects(size() - _free_head == size() - _desc_in_flight); + return size() - _desc_in_flight; + } + // access the current index virtq_desc& current() { @@ -246,65 +282,71 @@ class Virtio { return _queue.desc[ _queue.desc[_free_head].next ]; } - + // go to next index void go_next() { _free_head = _queue.desc[_free_head].next; } - - inline uint16_t size(){ return _size; } - + + uint16_t size() const noexcept + { + return _size; + } + + /** Inject a packet filter delegate the last possible point downstream */ + inline void on_exit_to_physical(delegate dlg) + { on_exit_to_physical_ = dlg; }; + + }; - + /** Get the Virtio config registers from the PCI device. - + @note it varies how these are structured, hence a void* buf */ void get_config(void* buf, int len); - + /** Get the (saved) device IRQ */ inline uint8_t irq(){ return _irq; }; /** Reset the virtio device */ void reset(); - + /** Negotiate supported features with host */ void negotiate_features(uint32_t features); - + /** Register interrupt handler & enable IRQ */ - //void enable_irq_handler(IRQ_handler::irq_delegate d); void enable_irq_handler(); /** Probe PCI device for features */ uint32_t probe_features(); - + /** Get locally stored features */ inline uint32_t features(){ return _features; }; - + /** Get iobase. Wrapper around PCI_Device::iobase */ inline uint32_t iobase(){ return _iobase; } /** Get queue size. @param index - the Virtio queue index */ - uint32_t queue_size(uint16_t index); - + uint32_t queue_size(uint16_t index); + /** Assign a queue descriptor to a PCI queue index */ bool assign_queue(uint16_t index, uint32_t queue_desc); - + /** Tell Virtio device if we're OK or not. Virtio Std. § 3.1.1,step 8*/ void setup_complete(bool ok); + /** Indicate which Virtio version (PCI revision ID) is supported. - /** Indicate which Virtio version (PCI revision ID) is supported. - Currently only Legacy is supported (partially the 1.0 standard) - */ + */ static inline bool version_supported(uint16_t i) { return i <= 0; } - - /** Virtio device constructor. - - Should conform to Virtio std. §3.1.1, steps 1-6 + + /** Virtio device constructor. + + Should conform to Virtio std. §3.1.1, steps 1-6 (Step 7 is "Device specific" which a subclass will handle) */ Virtio(hw::PCI_Device& pci); @@ -312,25 +354,24 @@ class Virtio private: //PCI memer as reference (so no indirection overhead) hw::PCI_Device& _pcidev; - + //We'll get this from PCI_device::iobase(), but that lookup takes longer - uint32_t _iobase = 0; - + uint32_t _iobase = 0; + uint8_t _irq = 0; uint32_t _features = 0; uint16_t _virtio_device_id = 0; - + // Indicate if virtio device ID is legacy or standard bool _LEGACY_ID = 0; bool _STD_ID = 0; - + void set_irq(); //TEST int calls = 0; - + void default_irq_handler(); }; #endif - diff --git a/api/virtio/virtionet.hpp b/api/virtio/virtionet.hpp index 4025822ee5..d1494feea8 100644 --- a/api/virtio/virtionet.hpp +++ b/api/virtio/virtionet.hpp @@ -6,29 +6,29 @@ // 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. -/** - @note This virtionet implementation was very much inspired by - SanOS, (C) Michael Ringgaard. All due respect. - - STANDARD: - - We're aiming for standard compliance: - - Virtio 1.0, OASIS Committee Specification Draft 01 - (http://docs.oasis-open.org/virtio/virtio/v1.0/csd01/virtio-v1.0-csd01.pdf) - - In the following abbreviated to Virtio 1.01 - - ...Alas, nobody's using it yet, so we're stuck with "legacy" for now. +/** + @note This virtionet implementation was very much inspired by + SanOS, (C) Michael Ringgaard. All due respect. + + STANDARD: + + We're aiming for standard compliance: + + Virtio 1.0, OASIS Committee Specification Draft 01 + (http://docs.oasis-open.org/virtio/virtio/v1.0/csd01/virtio-v1.0-csd01.pdf) + + In the following abbreviated to Virtio 1.01 + + ...Alas, nobody's using it yet, so we're stuck with "legacy" for now. */ #ifndef VIRTIO_VIRTIONET_HPP #define VIRTIO_VIRTIONET_HPP @@ -42,7 +42,7 @@ /** Virtio Net Features. From Virtio Std. 5.1.3 */ -/* Device handles packets with partial checksum. This “checksum offload” +/* Device handles packets with partial checksum. This “checksum offload” is a common feature on modern network cards.*/ #define VIRTIO_NET_F_CSUM 0 @@ -110,7 +110,59 @@ /** Virtio-net device driver. */ class VirtioNet : Virtio { - + +public: + + /** Human readable name. */ + const char* name(); + + /** Mac address. */ + const net::Ethernet::addr& mac(); + + constexpr uint16_t MTU() const { + return 1500; } + + constexpr uint16_t bufsize() const { + return MTU() + + sizeof(net::Ethernet::header) + sizeof(net::Ethernet::trailer) + + sizeof(virtio_net_hdr); } + + /** Delegate linklayer output. Hooks into IP-stack bottom, w.UPSTREAM data. */ + inline void set_linklayer_out(net::upstream link_out){ + _link_out = link_out; + //rx_q.set_data_handler(link_out); + }; + + inline net::upstream get_linklayer_out() + { return _link_out; } + + inline net::BufferStore& bufstore() { return bufstore_; } + + /** Linklayer input. Hooks into IP-stack bottom, w.DOWNSTREAM data.*/ + void transmit(net::Packet_ptr pckt); + + /** Constructor. @param pcidev an initialized PCI device. */ + VirtioNet(hw::PCI_Device& pcidev); + + inline void on_transmit_queue_available(net::transmit_avail_delg del) + { transmit_queue_available_event_ = del; }; + + /** Space available in the transmit queue, in packets */ + inline size_t transmit_queue_available(){ + return tx_q.num_free() / 2; + }; + + /** Number of incoming packets waiting in the RX-queue */ + inline size_t receive_queue_waiting(){ + return rx_q.new_incoming() / 2; + }; + + + inline void on_exit_to_physical(delegate dlg) + { on_exit_to_physical_ = dlg; }; + +private: + struct virtio_net_hdr { uint8_t flags; @@ -121,7 +173,7 @@ class VirtioNet : Virtio { uint16_t csum_offset; // Offset after that to place checksum }__attribute__((packed)); - /** Virtio std. § 5.1.6.1: + /** Virtio std. § 5.1.6.1: "The legacy driver only presented num_buffers in the struct virtio_net_hdr when VIRTIO_NET_F_MRG_RXBUF was not negotiated; without that feature the structure was 2 bytes shorter." */ struct virtio_net_hdr_nomerge { @@ -134,90 +186,67 @@ class VirtioNet : Virtio { uint16_t num_buffers; }__attribute__((packed)); - - /** An empty header. + + /** An empty header. It's ok to use as long as we don't need checksum offloading or other 'fancier' virtio features. */ - constexpr static virtio_net_hdr empty_header = {0,0,0,0,0,0}; - + constexpr static virtio_net_hdr empty_header = {0,0,0,0,0,0}; + Virtio::Queue rx_q; Virtio::Queue tx_q; Virtio::Queue ctrl_q; // Moved to Nic - // Ethernet eth; + // Ethernet eth; // Arp arp; // From Virtio 1.01, 5.1.4 struct config{ net::Ethernet::addr mac; uint16_t status; - + //Only valid if VIRTIO_NET_F_MQ - uint16_t max_virtq_pairs = 0; + uint16_t max_virtq_pairs = 0; }_conf; - + //sizeof(config) if VIRTIO_NET_F_MQ, else sizeof(config) - sizeof(uint16_t) int _config_length = sizeof(config); - + /** Get virtio PCI config. @see Virtio::get_config.*/ void get_config(); - - - /** Service the RX Queue. - Push incoming data up to linklayer, dequeue RX buffers. */ - void service_RX(); - - /** Service the TX Queue - Dequeue used TX buffers. @note: This function does not take any - responsibility for memory management. */ - void service_TX(); - - /** Handle device IRQ. - - Will look for config. changes and service RX/TX queues as necessary.*/ + + + /** Service the RX/TX Queues. + Push incoming data up to linklayer, dequeue any used RX- and TX buffers.*/ + void service_queues(); + + /** Add packet to buffer chain */ + void add_to_tx_buffer(net::Packet_ptr pckt); + + /** Add packet chain to virtio queue */ + void enqueue(net::Packet_ptr pckt); + + /** Handle device IRQ. + Will look for config changes and service RX/TX queues as necessary.*/ void irq_handler(); - + /** Allocate and queue buffer from bufstore_ in RX queue. */ - int add_receive_buffer(); + int add_receive_buffer(); /** Upstream delegate for linklayer output */ net::upstream _link_out; /** 20-bit / 1MB of buffers to start with */ - net::BufferStore bufstore_{ 0xfffffU / MTU(), MTU(), sizeof(virtio_net_hdr) }; - net::BufferStore::release_del release_buffer = + net::BufferStore bufstore_{ 0xfffffU / bufsize(), bufsize(), sizeof(virtio_net_hdr) }; + net::BufferStore::release_del release_buffer = net::BufferStore::release_del::from (bufstore_); - -public: - - /** Human readable name. */ - const char* name(); - - /** Mac address. */ - const net::Ethernet::addr& mac(); - - constexpr uint16_t MTU() const { - return 1500 + sizeof(virtio_net_hdr); } - - /** Delegate linklayer output. Hooks into IP-stack bottom, w.UPSTREAM data. */ - inline void set_linklayer_out(net::upstream link_out){ - _link_out = link_out; - //rx_q.set_data_handler(link_out); - }; - inline net::upstream get_linklayer_out() - { return _link_out; } - - inline net::BufferStore& bufstore() { return bufstore_; } - - /** Linklayer input. Hooks into IP-stack bottom, w.DOWNSTREAM data.*/ - void transmit(net::Packet_ptr pckt); - - /** Constructor. @param pcidev an initialized PCI device. */ - VirtioNet(hw::PCI_Device& pcidev); - + net::transmit_avail_delg transmit_queue_available_event_ {}; + + net::Packet_ptr transmit_queue_ {0}; + + delegate on_exit_to_physical_ {}; }; diff --git a/etc/batch_apply_editorconfig.sh b/etc/batch_apply_editorconfig.sh new file mode 100755 index 0000000000..9c3e4a9869 --- /dev/null +++ b/etc/batch_apply_editorconfig.sh @@ -0,0 +1,13 @@ +#! /bin/bash + +# NOTE: This script requires emacs, and the emacs editorconfig plugin +# It's loading the .emacs config located here: +# https://github.com/alfred-bratterud/emacs.d + +SRC=$1 + +for file in `find $SRC \( -not -path "*/cxxabi/*" -not -path "*/STREAM/*" -not -path "*/lest/*" -not -path "*/mod/*" \) -type f \( -name *.cpp -or -name *.hpp -or -name *.inc \)` +do + echo -e "\n >> Formatting $file" + emacs $file -batch -l ~/.emacs.d/.emacs -f format-buffer +done diff --git a/etc/build_llvm32.sh b/etc/build_llvm32.sh index e4f81b3c9a..62c4e75aae 100755 --- a/etc/build_llvm32.sh +++ b/etc/build_llvm32.sh @@ -9,16 +9,17 @@ trap 'echo -e "\nINSTALL FAILED ON COMMAND: $previous_command\n"' EXIT # llvm_src -> path to clone llvm repo # llvm_build-> path to build llvm-libs. (must beoutside of llvm) # INCLUDEOS_SRC -> InclueOS git source (i.e =$HOME/IncludeOS) +# newlib_inc -> Include-path for newlib headers # OPTIONALS (required the first time, but optional later): # -# $install_llvm_dependencies: required paackages, cmake, ninja etc. +# $install_llvm_dependencies: required paackages, cmake, ninja etc. # $download_llvm: Clone llvm svn sources IncludeOS_sys=$INCLUDEOS_SRC/api/sys - +libcxx_inc=$BUILD_DIR/$llvm_src/projects/libcxx/include if [ ! -z $install_llvm_dependencies ]; then @@ -27,34 +28,34 @@ if [ ! -z $install_llvm_dependencies ]; then fi if [ ! -z $download_llvm ]; then - # Clone LLVM + # Clone LLVM svn co http://llvm.org/svn/llvm-project/llvm/trunk $llvm_src - # git clone http://llvm.org/git/llvm - + # git clone http://llvm.org/git/llvm + # Clone CLANG - not necessary to build only libc++ and libc++abi - # cd llvm/tools + # cd llvm/tools # svn co http://llvm.org/svn/llvm-project/cfe/trunk clang # svn co http://llvm.org/svn/llvm-project/clang-tools-extra/trunk extra - + # Clone libc++, libc++abi, and some extra stuff (recommended / required for clang) cd $llvm_src/projects - + # Compiler-rt svn co http://llvm.org/svn/llvm-project/compiler-rt/trunk compiler-rt # git clone http://llvm.org/git/llvm compiler-rt - + # libc++abi svn co http://llvm.org/svn/llvm-project/libcxxabi/trunk libcxxabi # git clone http://llvm.org/git/libcxxabi - + # libc++ svn co http://llvm.org/svn/llvm-project/libcxx/trunk libcxx # git clone http://llvm.org/git/libcxx - + # libunwind svn co http://llvm.org/svn/llvm-project/libunwind/trunk libunwind #git clone http://llvm.org/git/libunwind - + # Back to start cd ../../ fi @@ -79,12 +80,6 @@ OPTS+=-DCMAKE_BUILD_TYPE=MinSizeRel" " OPTS+=-DCMAKE_C_COMPILER=clang-$clang_version" " OPTS+=-DCMAKE_CXX_COMPILER=clang++-$clang_version" " # -std=c++11" " -# -# WARNING: It seems imossible to pass in cxx-flags like this; I've tried \' \\" \\\" etc. -# -# OPTS+="-DCMAKE_CXX_FLAGS='-I/home/alfred/IncludeOS/stdlib/support -I/usr/local/IncludeOS/i686-elf/include -I/home/alfred/IncludeOS/stdlib/support/newlib -I/home/alfred/IncludeOS/src/include' " -# OPTS+='-DCMAKE_CXX_FLAGS=-I/home/alfred/IncludeOS/stdlib/support -I/usr/local/IncludeOS/i686-elf/include -I/home/alfred/IncludeOS/stdlib/support/newlib -I/home/alfred/IncludeOS/src/include ' - TRIPLE=i686-pc-none-elf OPTS+=-DTARGET_TRIPLE=$TRIPLE" " @@ -113,31 +108,24 @@ OPTS+=-LIBCXXABI_USE_LLVM_UNWINDER=ON" " echo "LLVM CMake Build options:" $OPTS +# CMAKE +# Using Ninja (slightly faster, but not by much) # -# WARNING: The following will cause the "requires std::atomic" error. -# (For some reason - the headers should be the same as in llbm/projects/libcxx/include - our mods causes this?" # -# -I/$IncludeOS_Source/IncludeOS/stdlib - -# Various search-path stuff -# -nostdinc -nostdlib -nodefaultlibs -ffreestanding -isystem /usr/local/IncludeOS/i686/ --sysroot=/usr/local/IncludeOS/i686 - - +# NOTE: It seems impossible to pass in cxx-flags like this; I've tried \' \\" \\\" etc. # -# CONFIGURE +# OPTS+="-DCMAKE_CXX_FLAGS='-I/home/alfred/IncludeOS/stdlib/support -I/usr/local/IncludeOS/i686-elf/include -I/home/alfred/IncludeOS/stdlib/support/newlib -I/home/alfred/IncludeOS/src/include' " +# OPTS+='-DCMAKE_CXX_FLAGS=-I/home/alfred/IncludeOS/stdlib/support -I/usr/local/IncludeOS/i686-elf/include -I/home/alfred/IncludeOS/stdlib/support/newlib -I/home/alfred/IncludeOS/src/include ' # -# Using makefiles -# time cmake -G"Unix Makefiles" $OPTS ../$llvm_src - -llvm_src_verbose=-v -libcxx_inc=$BUILD_DIR/$llvm_src/projects/libcxx/include - -# Using Ninja (slightly faster, but not by much) -cmake -GNinja $OPTS -DCMAKE_CXX_FLAGS="-std=c++11 $llvm_src_verbose -I$IncludeOS_sys -I$libcxx_inc -I$INCLUDEOS_SRC/api -I$newlib_inc -I$INCLUDEOS_SRC/src/include/ -I$INCLUDEOS_SRC/stdlib/support/newlib/ " $BUILD_DIR/$llvm_src # -DCMAKE_CXX_COMPILER='clang++ -std=c++11 +# Include-path ordering: +# 1. IncludeOS_sys has to come first, as it provides lots of C11 prototypes that libc++ relies on, but which newlib does not provide (see our math.h) +# 2. libcxx_inc must come before newlib, due to math.h function wrappers around C99 macros (signbit, nan etc) +# 3. newlib_inc provodes standard C headers +cmake -GNinja $OPTS -DCMAKE_CXX_FLAGS="-std=c++11 $llvm_src_verbose -I$IncludeOS_sys -I$libcxx_inc -I$newlib_inc" $BUILD_DIR/$llvm_src # -# MAKE +# MAKE # # Using ninja # diff --git a/etc/copy_scripts.sh b/etc/copy_scripts.sh index bdd5971ff9..bcb9f7303c 100755 --- a/etc/copy_scripts.sh +++ b/etc/copy_scripts.sh @@ -1,9 +1,9 @@ #!/bin/bash INCLUDEOS_SRC=${INCLUDEOS_SRC-$HOME/IncludeOS/} -INSTALL_DIR=${INSTALL_DIR-$HOME/IncludeOS_install} -mkdir -p $INSTALL_DIR/etc -cp $INCLUDEOS_SRC/etc/qemu-ifup $INSTALL_DIR/etc/ -cp $INCLUDEOS_SRC/etc/qemu_cmd.sh $INSTALL_DIR/etc/ -cp $INCLUDEOS_SRC/etc/run.sh $INSTALL_DIR/etc/ - +INCLUDEOS_INSTALL=${INCLUDEOS_INSTALL-$HOME/IncludeOS_install} +mkdir -p $INCLUDEOS_INSTALL/etc +cp $INCLUDEOS_SRC/etc/qemu-ifup $INCLUDEOS_INSTALL/etc/ +cp $INCLUDEOS_SRC/etc/qemu_cmd.sh $INCLUDEOS_INSTALL/etc/ +cp $INCLUDEOS_SRC/etc/run.sh $INCLUDEOS_INSTALL/etc/ +cp $INCLUDEOS_SRC/etc/create_memdisk.sh $INCLUDEOS_INSTALL/etc/ diff --git a/etc/create_binary_bundle.sh b/etc/create_binary_bundle.sh index d883c6c9f3..27364bb8eb 100755 --- a/etc/create_binary_bundle.sh +++ b/etc/create_binary_bundle.sh @@ -1,8 +1,11 @@ #! /bin/bash # Zip-file name +[ ! -v INCLUDEOS_SRC ] && INCLUDEOS_SRC=$HOME/IncludeOS +pushd $INCLUDEOS_SRC tag=`git describe --abbrev=0` filename_tag=`echo $tag | tr . -` +popd # Where to place the installation bundle DIR_NAME="IncludeOS_install" @@ -13,7 +16,7 @@ DIR_NAME="IncludeOS_install" echo ">>> Creating Installation Bundle as $INSTALL_DIR" -OUTFILE="$DIR_NAME_$filename_tag.tar.gz" +OUTFILE="${DIR_NAME}_$filename_tag.tar.gz" newlib=$TEMP_INSTALL_DIR/i686-elf/lib llvm=$BUILD_DIR/build_llvm diff --git a/etc/create_memdisk.sh b/etc/create_memdisk.sh new file mode 100755 index 0000000000..004b5a496c --- /dev/null +++ b/etc/create_memdisk.sh @@ -0,0 +1,58 @@ +#! /bin/bash +# +# Stuff everything in the ./memdisk directory into a brand new +# FAT-formatted disk image, $FILENAME. +# +# WARNING: +# * Existing disk image of the same name gets overwritten +# * An assembly-file 'memdisk.asm' gets created / overwritten +# +# NOTE: +# * A temporary mount-point $MOUNT gets created then removed + +if [ ! -d memdisk ] +then + echo "Directory 'memdisk' not found" + exit 1 +fi + +BLOCK_SIZE=512 +MIN_BLOCKS=34 +MIN_ROOT_ENTS=16 +FILENAME=memdisk.fat +MOUNT=temp_mount +ASM_FILE=memdisk.asm + +# The assembly trick to get the memdisk into the ELF binary +cat > $ASM_FILE <<-EOF +USE32 +ALIGN 32 + +section .diskdata +contents: + incbin "$FILENAME" + +EOF + +size=`du -s --block-size=$BLOCK_SIZE memdisk | cut -f1` +echo -e "\n>>> Memdisk requires $size blocks, minimum block-count is $MIN_BLOCKS" + +[ $size -gt $MIN_BLOCKS ] || size=$MIN_BLOCKS + +echo -e ">>> Creating $size blocks" + +root_entries=`ls -l memdisk | wc -l` +[ $root_entries -gt $MIN_ROOT_ENTS ] || root_entries=$MIN_ROOT_ENTS + +echo -e ">>> Creating $root_entries root entries" + +[ -e $FILENAME ] && rm $FILENAME + +mkfs.fat -C $FILENAME -S $BLOCK_SIZE $size -n "INC_MEMDISK" -r $root_entries + +mkdir -p $MOUNT +sudo mount $FILENAME $MOUNT +sudo cp -r memdisk/* $MOUNT/ +sync +sudo umount $MOUNT +rmdir $MOUNT diff --git a/etc/cross_compiler.sh b/etc/cross_compiler.sh index 198c476053..24f18a3f72 100755 --- a/etc/cross_compiler.sh +++ b/etc/cross_compiler.sh @@ -4,9 +4,11 @@ mkdir -p $BUILD_DIR cd $BUILD_DIR +GCC_LOC=ftp://ftp.nluug.nl/mirror/languages/gcc/releases/ + if [ ! -f gcc-$gcc_version.tar.gz ]; then echo -e "\n\n >>> Getting GCC \n" - wget -c --trust-server-name ftp://ftp.uninett.no/pub/gnu/gcc/gcc-$gcc_version/gcc-$gcc_version.tar.gz + wget -c --trust-server-name $GCC_LOC/gcc-$gcc_version/gcc-$gcc_version.tar.gz fi # UNPACK GCC diff --git a/etc/install_from_bundle.sh b/etc/install_from_bundle.sh index 64dbd513d2..1e9e618cfb 100755 --- a/etc/install_from_bundle.sh +++ b/etc/install_from_bundle.sh @@ -2,22 +2,24 @@ # Install the IncludeOS libraries (i.e. IncludeOS_home) from binary bundle # ...as opposed to building them all from scratch, which takes a long time -# # -# OPTIONS: +# +# OPTIONS: # # Location of the IncludeOS repo: # $ export INCLUDEOS_SRC=your/github/cloned/IncludeOS # # Parent directory of where you want the IncludeOS libraries (i.e. IncludeOS_home) -# $ export INCLUDEOS_INSTALL_LOC=parent/folder/for/IncludeOS/libraries i.e. +# $ export INCLUDEOS_INSTALL_LOC=parent/folder/for/IncludeOS/libraries i.e. [ ! -v INCLUDEOS_SRC ] && export INCLUDEOS_SRC=$(readlink -f "$(dirname "$0")/..") [ ! -v INCLUDEOS_INSTALL_LOC ] && export INCLUDEOS_INSTALL_LOC=$HOME export INCLUDEOS_HOME=$INCLUDEOS_INSTALL_LOC/IncludeOS_install # Install dependencies -DEPENDENCIES="curl make clang-3.6 nasm bridge-utils qemu" +$INCLUDEOS_SRC/etc/prepare_ubuntu_deps.sh + +DEPENDENCIES="curl make clang-3.8 nasm bridge-utils qemu" echo ">>> Installing dependencies (requires sudo):" echo " Packages: $DEPENDENCIES" sudo apt-get update @@ -27,21 +29,21 @@ sudo apt-get install -y $DEPENDENCIES echo ">>> Updating git-tags " # Get the latest tag from IncludeOS repo pushd $INCLUDEOS_SRC -git pull --tags +git fetch --tags tag=`git describe --abbrev=0` -popd +popd filename_tag=`echo $tag | tr . -` filename="IncludeOS_install_"$filename_tag".tar.gz" -# If the tarball exists, use that -if [ -e $filename ] +# If the tarball exists, use that +if [ -e $filename ] then echo -e "\n\n>>> IncludeOS tarball exists - extracting to $INCLUDEOS_INSTALL_LOC" tar -C $INCLUDEOS_INSTALL_LOC -xzf $filename -else +else echo -e "\n\n>>> Downloading IncludeOS release tarball from GitHub" - # Download from GitHub API + # Download from GitHub API if [ "$1" = "-oauthToken" ] then oauthToken=$2 @@ -61,11 +63,17 @@ else else curl -H "Accept: application/octet-stream" -L -o $filename $ASSET_URL fi - + echo -e "\n\n>>> Fetched tarball - extracting to $INCLUDEOS_INSTALL_LOC" - tar -C $INCLUDEOS_INSTALL_LOC -xzf $filename + tar -C $INCLUDEOS_INSTALL_LOC -xzf $filename fi +echo -e "\n\n>>> Installing submodules" +pushd $INCLUDEOS_SRC +git submodule init +git submodule update +popd + echo -e "\n\n>>> Building IncludeOS" pushd $INCLUDEOS_SRC/src make -j @@ -84,6 +92,3 @@ sudo $INCLUDEOS_SRC/etc/create_bridge.sh $INCLUDEOS_SRC/etc/copy_scripts.sh echo -e "\n\n>>> Done! Test your installation with ./test.sh" - - - diff --git a/etc/install_osx.sh b/etc/install_osx.sh index ce49a09584..fce3d682e8 100755 --- a/etc/install_osx.sh +++ b/etc/install_osx.sh @@ -2,15 +2,15 @@ # Install the IncludeOS libraries (i.e. IncludeOS_home) from binary bundle # ...as opposed to building them all from scratch, which takes a long time -# # -# OPTIONS: +# +# OPTIONS: # # Location of the IncludeOS repo: # $ export INCLUDEOS_SRC=your/github/cloned/IncludeOS # # Parent directory of where you want the IncludeOS libraries (i.e. IncludeOS_home) -# $ export INCLUDEOS_INSTALL_LOC=parent/folder/for/IncludeOS/libraries i.e. +# $ export INCLUDEOS_INSTALL_LOC=parent/folder/for/IncludeOS/libraries i.e. [[ -z $INCLUDEOS_SRC ]] && export INCLUDEOS_SRC=`pwd` [[ -z $INCLUDEOS_INSTALL_LOC ]] && export INCLUDEOS_INSTALL_LOC=$HOME @@ -22,22 +22,23 @@ echo -e "\n###################################" echo -e "IncludeOS installation for Mac OS X" echo -e "###################################" -echo -e "\n# Prequisites:\n - - homebrew (OSX package manager - https://brew.sh) +echo -e "\n# Prequisites:\n + - homebrew (OSX package manager - https://brew.sh) - \`/usr/local\` directory with write access - - \`/usr/local/bin\` added to your PATH" + - \`/usr/local/bin\` added to your PATH + - (Recommended) XCode CTL (Command Line Tools)" ### DEPENDENCIES ### echo -e "\n# Dependencies" ## LLVM ## -echo -e "\nllvm36 (clang/clang++ 3.6) - required for compiling" +echo -e "\nllvm38 (clang/clang++ 3.8) - required for compiling" DEPENDENCY_LLVM=false -BREW_LLVM=llvm36 -BREW_CLANG_CC=/usr/local/bin/clang-3.6 -BREW_CLANG_CPP=/usr/local/bin/clang++-3.6 +BREW_LLVM=llvm38 +BREW_CLANG_CC=/usr/local/bin/clang-3.8 +BREW_CLANG_CPP=/usr/local/bin/clang++-3.8 [ -e $BREW_CLANG_CPP ] && DEPENDENCY_LLVM=true if ($DEPENDENCY_LLVM); then echo -e "> Found"; else echo -e "> Not Found"; fi @@ -46,6 +47,9 @@ function install_llvm { echo -e "\n>>> Installing: llvm" # Check if brew is installed command -v brew >/dev/null 2>&1 || { echo >&2 " Cannot find brew! Visit http://brew.sh/ for how-to install. Aborting."; exit 1; } + # Try to update brew + echo -e "\n> Make sure homebrew is up to date." + brew update # Install llvm echo -e "\n> Install $BREW_LLVM with brew" brew install $BREW_LLVM @@ -74,19 +78,19 @@ function install_binutils { # Create build directory if not exist mkdir -p $INCLUDEOS_BUILD - + # Decide filename (release) BINUTILS_RELEASE=binutils-2.25 filename_binutils=$BINUTILS_RELEASE".tar.gz" # Check if file is downloaded if [ -e $INCLUDEOS_BUILD/$filename_binutils ] - then + then echo -e "\n> $BINUTILS_RELEASE already downloaded." else # Download binutils echo -e "\n> Downloading $BINUTILS_RELEASE." - curl https://ftp.gnu.org/gnu/binutils/$filename_binutils -o $INCLUDEOS_BUILD/$filename_binutils + curl https://ftp.gnu.org/gnu/binutils/$filename_binutils -o $INCLUDEOS_BUILD/$filename_binutils fi ## Unzip @@ -95,7 +99,7 @@ function install_binutils { ## Configure pushd $INCLUDEOS_BUILD/$BINUTILS_RELEASE - + ## Install echo -e "\n> Installing $BINUTILS_RELEASE to $BINUTILS_DIR" ./configure --program-prefix=$LINKER_PREFIX --prefix=$BINUTILS_DIR --enable-multilib --enable-ld=yes --target=i686-elf --disable-werror --enable-silent-rules @@ -130,6 +134,12 @@ function install_nasm { echo -e "\n>>> Done installing: nasm" } +## WARN ABOUT XCODE CTL ## +if ! [[ $(xcode-select -p) ]] +then + echo -e "\nWARNING: Command Line Tools don't seem to be installed, installation MAY not complete. + Install with: xcode-select --install" +fi ### INSTALL ### echo @@ -160,18 +170,23 @@ echo ">>> Updating git-tags " pushd $INCLUDEOS_SRC git fetch --tags tag=`git describe --abbrev=0` -popd + +echo -e "\n>>> Fetching git submodules " +git submodule init +git submodule update + +popd filename_tag=`echo $tag | tr . -` filename="IncludeOS_install_"$filename_tag".tar.gz" -# If the tarball exists, use that -if [ -e $filename ] +# If the tarball exists, use that +if [ -e $filename ] then echo -e "\n\n>>> IncludeOS tarball exists - extracting to $INCLUDEOS_INSTALL_LOC" -else +else echo -e "\n\n>>> Downloading IncludeOS release tarball from GitHub" - # Download from GitHub API + # Download from GitHub API if [ "$1" = "-oauthToken" ] then oauthToken=$2 @@ -191,7 +206,7 @@ else else curl -H "Accept: application/octet-stream" -L -o $filename $ASSET_URL fi - + echo -e "\n\n>>> Fetched tarball - extracting to $INCLUDEOS_INSTALL_LOC" fi diff --git a/etc/newlib_clang.patch b/etc/newlib_clang.patch index 2d6f34822b..c9f9327984 100644 --- a/etc/newlib_clang.patch +++ b/etc/newlib_clang.patch @@ -1,6 +1,6 @@ -diff -Naur newlib-2.2.0-1/newlib/libm/machine/i386/f_ldexp.S newlib-2.2.0-1_patched/newlib/libm/machine/i386/f_ldexp.S ---- newlib-2.2.0-1/newlib/libm/machine/i386/f_ldexp.S 2002-12-20 22:31:20.000000000 +0100 -+++ newlib-2.2.0-1_patched/newlib/libm/machine/i386/f_ldexp.S 2015-11-01 17:00:59.215426521 +0100 +diff -Naur newlib-2.4.0/newlib/libm/machine/i386/f_ldexp.S newlib-2.4.0_patched/newlib/libm/machine/i386/f_ldexp.S +--- newlib-2.4.0/newlib/libm/machine/i386/f_ldexp.S 2002-12-20 22:31:20.000000000 +0100 ++++ newlib-2.4.0_patched/newlib/libm/machine/i386/f_ldexp.S 2015-11-01 17:00:59.215426521 +0100 @@ -27,7 +27,7 @@ SYM (_f_ldexp): pushl ebp @@ -10,9 +10,9 @@ diff -Naur newlib-2.2.0-1/newlib/libm/machine/i386/f_ldexp.S newlib-2.2.0-1_patc fldl 8(ebp) fscale fstp st1 -diff -Naur newlib-2.2.0-1/newlib/libm/machine/i386/f_ldexpf.S newlib-2.2.0-1_patched/newlib/libm/machine/i386/f_ldexpf.S ---- newlib-2.2.0-1/newlib/libm/machine/i386/f_ldexpf.S 2002-12-20 22:31:20.000000000 +0100 -+++ newlib-2.2.0-1_patched/newlib/libm/machine/i386/f_ldexpf.S 2015-11-01 16:58:23.646121581 +0100 +diff -Naur newlib-2.4.0/newlib/libm/machine/i386/f_ldexpf.S newlib-2.4.0_patched/newlib/libm/machine/i386/f_ldexpf.S +--- newlib-2.4.0/newlib/libm/machine/i386/f_ldexpf.S 2002-12-20 22:31:20.000000000 +0100 ++++ newlib-2.4.0_patched/newlib/libm/machine/i386/f_ldexpf.S 2015-11-01 16:58:23.646121581 +0100 @@ -27,7 +27,7 @@ SYM (_f_ldexpf): pushl ebp diff --git a/etc/prepare_ubuntu_deps.sh b/etc/prepare_ubuntu_deps.sh new file mode 100755 index 0000000000..beb2815d6c --- /dev/null +++ b/etc/prepare_ubuntu_deps.sh @@ -0,0 +1,26 @@ +# Prepare Ubuntu for installing dependencies +# +# Older versions don't have clang-3.8 in their apt source lists + +INCLUDEOS_SRC=${INCLUDEOS_SRC-$HOME/IncludeOS/} +clang_version=${clang_version-3.8} + +UBUNTU_VERSION=`lsb_release -rs` +UBUNTU_CODENAME=`lsb_release -cs` + +if [ $(echo "$UBUNTU_VERSION < 15.10" | bc) -eq 1 ] +then + + DEB_LINE="deb http://llvm.org/apt/$UBUNTU_CODENAME/ llvm-toolchain-$UBUNTU_CODENAME-$clang_version main" + + LINE_EXISTS=$(cat /etc/apt/sources.list | grep -c "$DEB_LINE") + + if [ $LINE_EXISTS -lt 1 ] + then + echo ">>> Adding clang $clang_version to your /etc/apt/sources.list" + wget -O - http://llvm.org/apt/llvm-snapshot.gpg.key|sudo apt-key add - + + echo $DEB_LINE | sudo tee -a /etc/apt/sources.list + sudo apt-get update + fi +fi diff --git a/etc/qemu_cmd.sh b/etc/qemu_cmd.sh index 565def0e0b..059677b4bb 100755 --- a/etc/qemu_cmd.sh +++ b/etc/qemu_cmd.sh @@ -10,14 +10,17 @@ fi export macaddress="c0:01:0a:00:00:2a" INCLUDEOS_HOME=${INCLUDEOS_HOME-$HOME/IncludeOS_install} -export qemu_ifup="$INCLUDEOS_HOME/etc/qemu-ifup" - -export DEV_NET="-device virtio-net,netdev=net0,mac=$macaddress -netdev tap,id=net0,script=$qemu_ifup" -export SMP="-smp 1" -export DEV_GRAPHICS="-nographic" +export qemu_ifup="$INCLUDEOS_HOME/etc/qemu-ifup" -export SERIAL="-virtioconsole stdio" +[ ! -v NET ] && export NET="-device virtio-net,netdev=net0,mac=$macaddress -netdev tap,id=net0,script=$qemu_ifup" +[ ! -v SMP ] && export SMP="-smp 1" +[ ! -v GRAPHICS ] && export GRAPHICS="-nographic" +[ ! -v SERIAL ] && export SERIAL="-virtioconsole stdio" +[ ! -v MEM ] && export MEM="-m 128" +[ ! -v HDA ] && export HDA="-drive file=$IMAGE,format=raw,if=ide" +[ ! -v HDB ] && export HDB="" +[ ! -v HDD ] && export HDD="$HDA $HDB" +[ ! -v CPU ] && export CPU="" -export DEV_HDD="-drive file=$IMAGE,format=raw,if=ide" -export QEMU_OPTS="$DEV_HDD $DEV_NET $DEV_GRAPHICS $SMP" +export QEMU_OPTS="$HDD $NET $GRAPHICS $SMP $MEM $CPU" diff --git a/etc/vboxrun.sh b/etc/vboxrun.sh index bdc786d487..dd84186958 100755 --- a/etc/vboxrun.sh +++ b/etc/vboxrun.sh @@ -91,6 +91,10 @@ else # NETWORK $VB modifyvm "$VMNAME" --nic1 hostonly --nictype1 virtio --hostonlyadapter1 vboxnet0 + + # Memory + $VB modifyvm "$VMNAME" --memory 256 + fi # START VM $VB startvm "$VMNAME" & diff --git a/examples/demo_service/Makefile b/examples/demo_service/Makefile index 4af7bc3582..8c04f9c522 100644 --- a/examples/demo_service/Makefile +++ b/examples/demo_service/Makefile @@ -4,133 +4,21 @@ # The name of your service SERVICE = IncludeOS_Demo_Service +SERVICE_NAME = IncludeOS Demo Service # Your service parts FILES = service.cpp -# IncludeOS location -ifndef INCLUDEOS_INSTALL - INCLUDEOS_INSTALL=$(HOME)/IncludeOS_install -endif - -# Shorter name -INSTALL = $(INCLUDEOS_INSTALL) - -# Compiler/Linker -################################################### - -OPTIONS = -Ofast -msse3 -Wall -Wextra -mstackrealign - -# External Libraries -################################################### -LIBC_OBJ = $(INSTALL)/newlib/libc.a -LIBG_OBJ = $(INSTALL)/newlib/libg.a -LIBM_OBJ = $(INSTALL)/newlib/libm.a - -LIBGCC = $(INSTALL)/libgcc/libgcc.a -LIBCXX = $(INSTALL)/libcxx/libc++.a $(INSTALL)/libcxx/libc++abi.a - - -INC_NEWLIB=$(INSTALL)/newlib/include -INC_LIBCXX=$(INSTALL)/libcxx/include +# Your disk image +DISK= -DEBUG_OPTS = -ggdb3 -v +# Your own include-path +LOCAL_INCLUDES= -CPP = clang++-3.6 -target i686-elf -ifndef LD_INC - LD_INC = ld +# IncludeOS location +ifndef INCLUDEOS_INSTALL +INCLUDEOS_INSTALL=$(HOME)/IncludeOS_install endif -INCLUDES = -I$(INC_LIBCXX) -I$(INSTALL)/api/sys -I$(INC_NEWLIB) -I$(INSTALL)/api - -CAPABS_COMMON = -msse3 -mstackrealign # Needed for 16-byte stack alignment (SSE) - -all: CAPABS = $(CAPABS_COMMON) -O2 -debug: CAPABS = $(CAPABS_COMMON) -O0 -stripped: CAPABS = $(CAPABS_COMMON) -Oz - -WARNS = -Wall -Wextra #-pedantic -CPPOPTS = $(CAPABS) $(WARNS) -c -m32 -std=c++14 -fno-stack-protector $(INCLUDES) -D_LIBCPP_HAS_NO_THREADS=1 #-flto -fno-exceptions - -LDOPTS = -nostdlib -melf_i386 -N --script=$(INSTALL)/linker.ld -flto - - -# Objects -################################################### - -CRTBEGIN_OBJ = $(INSTALL)/crt/crtbegin.o -CRTEND_OBJ = $(INSTALL)/crt/crtend.o -CRTI_OBJ = $(INSTALL)/crt/crti.o -CRTN_OBJ = $(INSTALL)/crt/crtn.o - -# Full link list -OBJS = $(FILES:.cpp=.o) .service_name.o -LIBS = $(INSTALL)/os.a $(LIBCXX) $(INSTALL)/os.a $(LIBC_OBJ) $(LIBM_OBJ) $(LIBGCC) - -OS_PRE = $(CRTBEGIN_OBJ) $(CRTI_OBJ) -OS_POST = $(CRTEND_OBJ) $(CRTN_OBJ) - -DEPS = $(OBJS:.o=.d) - -# Complete bulid -################################################### -# A complete build includes: -# - a "service", to be linked with OS-objects (OS included) - -all: service - -stripped: LDOPTS += -S #strip all -stripped: CPPOPTS += -Oz -stripped: service - - -# The same, but with debugging symbols (OBS: Dramatically increases binary size) -debug: CCOPTS += $(DEBUG_OPTS) -debug: CPPOPTS += $(DEBUG_OPTS) -debug: LDOPTS += -M --verbose - -debug: OBJS += $(LIBG_OBJ) - -debug: service #Don't wanna call 'all', since it strips debug info - -# Service -################################################### -service.o: service.cpp - @echo "\n>> Compiling the service" - $(CPP) $(CPPOPTS) -o $@ $< - -.service_name.o: $(INSTALL)/service_name.cpp - $(CPP) $(CPPOPTS) -DSERVICE_NAME="\"$(SERVICE)\"" -o $@ $< - -# Link the service with the os -service: $(OBJS) $(LIBS) - @echo "\n>> Linking service with OS" - $(LD_INC) $(LDOPTS) $(OS_PRE) $(OBJS) $(LIBS) $(OS_POST) -o $(SERVICE) - @echo "\n>> Building image " $(SERVICE).img - $(INSTALL)/vmbuild $(INSTALL)/bootloader $(SERVICE) - -# Object files -################################################### - -# Runtime -crt%.o: $(INSTALL)/crt/crt%.s - @echo "\n>> Assembling C runtime:" $@ - $(CPP) $(CPPOPTS) -x assembler-with-cpp $< - -# General C++-files to object files -%.o: %.cpp - @echo "\n>> Compiling OS object without header" - $(CPP) $(CPPOPTS) -o $@ $< - -# AS-assembled object files -%.o: %.s - @echo "\n>> Assembling GNU 'as' files" - $(CPP) $(CPPOPTS) -x assembler-with-cpp $< - -# Cleanup -################################################### -clean: - $(RM) $(OBJS) $(DEPS) $(SERVICE) - $(RM) $(SERVICE).img - --include $(DEPS) +# Include the installed seed makefile +include $(INCLUDEOS_INSTALL)/Makeseed diff --git a/examples/demo_service/run.sh b/examples/demo_service/run.sh index 3b2132d2cb..ca8b7d3193 100755 --- a/examples/demo_service/run.sh +++ b/examples/demo_service/run.sh @@ -1,3 +1,2 @@ #! /bin/bash -source ${INCLUDEOS_HOME-$HOME/IncludeOS_install}/etc/run.sh - +source ${INCLUDEOS_HOME-$HOME/IncludeOS_install}/etc/run.sh IncludeOS_Demo_Service.img diff --git a/examples/demo_service/service.cpp b/examples/demo_service/service.cpp index a05b54f26d..6a599011ec 100644 --- a/examples/demo_service/service.cpp +++ b/examples/demo_service/service.cpp @@ -6,9 +6,9 @@ // 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. @@ -24,81 +24,94 @@ // An IP-stack object std::unique_ptr > inet; +using namespace std::chrono; + +std::string HTML_RESPONSE() { + int color = rand(); + std::stringstream stream; + + /* HTML Fonts */ + std::string ubuntu_medium = "font-family: \'Ubuntu\', sans-serif; font-weight: 500; "; + std::string ubuntu_normal = "font-family: \'Ubuntu\', sans-serif; font-weight: 400; "; + std::string ubuntu_light = "font-family: \'Ubuntu\', sans-serif; font-weight: 300; "; + + /* HTML */ + stream << "" + << "" + << "" + << "

> 8) << "\">" + << "IncludeOS

" + << "

Now speaks TCP!

" + // .... generate more dynamic content + << "

...and can improvise http. With limitations of course, but it's been easier than expected so far

" + << "

© 2015, Oslo and Akershus University College of Applied Sciences
" + << "\n"; + + std::string html = stream.str(); + + std::string header="HTTP/1.1 200 OK \n " \ + "Date: Mon, 01 Jan 1970 00:00:01 GMT \n" \ + "Server: IncludeOS prototype 4.0 \n" \ + "Last-Modified: Wed, 08 Jan 2003 23:11:55 GMT \n" \ + "Content-Type: text/html; charset=UTF-8 \n" \ + "Content-Length: "+std::to_string(html.size())+"\n" \ + "Accept-Ranges: bytes\n" \ + "Connection: close\n\n"; + return header + html; +} + void Service::start() { // Assign a driver (VirtioNet) to a network interface (eth0) // @note: We could determine the appropirate driver dynamically, but then we'd // have to include all the drivers into the image, which we want to avoid. hw::Nic& eth0 = hw::Dev::eth<0,VirtioNet>(); - + // Bring up a network stack, attached to the nic // @note : No parameters after 'nic' means we'll use DHCP for IP config. inet = std::make_unique >(eth0); - + // Static IP configuration, until we (possibly) get DHCP // @note : Mostly to get a robust demo service that it works with and without DHCP - inet->network_config( {{ 10,0,0,42 }}, // IP - {{ 255,255,255,0 }}, // Netmask - {{ 10,0,0,1 }}, // Gateway - {{ 8,8,8,8 }} ); // DNS - + inet->network_config( { 10,0,0,42 }, // IP + { 255,255,255,0 }, // Netmask + { 10,0,0,1 }, // Gateway + { 8,8,8,8 } ); // DNS + srand(OS::cycles_since_boot()); // Set up a TCP server on port 80 auto& server = inet->tcp().bind(80); - + + hw::PIT::instance().onRepeatedTimeout(30s, []{ + printf(" TCP STATUS:\n%s \n", inet->tcp().status().c_str()); + }); + // Add a TCP connection handler - here a hardcoded HTTP-service server.onAccept([](auto conn) -> bool { - printf(" @onAccept - Connection attempt from: %s \n", - conn->to_string().c_str()); - printf(" Status: %s \n", conn->to_string().c_str()); + printf(" @onAccept - Connection attempt from: %s \n", + conn->to_string().c_str()); return true; // allow all connections - - }).onConnect([](auto conn) { - printf(" @onConnect - Connection successfully established. \n"); - printf(" TCP STATUS:\n%s \n", conn->host().status().c_str()); - - }).onReceive([](auto conn, bool push) { - std::string data = conn->read(1024); - printf(" @onData - PUSH: %d, Data read: \n%s\n", push, data.c_str()); - printf(" Status: %s \n", conn->to_string().c_str()); - int color = rand(); - std::stringstream stream; - - /* HTML Fonts */ - std::string ubuntu_medium = "font-family: \'Ubuntu\', sans-serif; font-weight: 500; "; - std::string ubuntu_normal = "font-family: \'Ubuntu\', sans-serif; font-weight: 400; "; - std::string ubuntu_light = "font-family: \'Ubuntu\', sans-serif; font-weight: 300; "; - - /* HTML */ - stream << "" - << "" - << "" - << "

> 8) << "\">" - << "IncludeOS

" - << "

Now speaks TCP!

" - // .... generate more dynamic content - << "

...and can improvise http. With limitations of course, but it's been easier than expected so far

" - << "

© 2015, Oslo and Akershus University College of Applied Sciences
" - << "\n"; - - /* HTTP-header */ - std::string html = stream.str(); - std::string header="HTTP/1.1 200 OK \n " \ - "Date: Mon, 01 Jan 1970 00:00:01 GMT \n" \ - "Server: IncludeOS prototype 4.0 \n" \ - "Last-Modified: Wed, 08 Jan 2003 23:11:55 GMT \n" \ - "Content-Type: text/html; charset=UTF-8 \n" \ - "Content-Length: "+std::to_string(html.size())+"\n" \ - "Accept-Ranges: bytes\n" \ - "Connection: close\n\n"; - - std::string output{header + html}; - conn->write(output.data(), output.size()); - - }).onDisconnect([](auto conn, std::string msg) { - printf(" @onDisconnect - Reason: %s \n", msg.c_str()); - printf(" TCP STATUS:\n%s \n", conn->host().status().c_str()); - }); + + }).onConnect([](auto conn) { + printf(" @onConnect - Connection successfully established.\n"); + // read async with a buffer size of 1024 bytes + // define what to do when data is read + conn->read(1024, [conn](net::TCP::buffer_t buf, size_t n) { + // create string from buffer + std::string data { (char*)buf.get(), n }; + printf(" @read:\n%s\n", data.c_str()); + + // create response + std::string response = HTML_RESPONSE(); + // write the data from the string with the strings size + conn->write(response.data(), response.size(), [](size_t n) { + printf(" @write: %u bytes written\n", n); + }); + }); + + }).onDisconnect([](auto, auto reason) { + printf(" @onDisconnect - Reason: %s \n", reason.to_string().c_str()); + }); printf("*** TEST SERVICE STARTED *** \n"); } diff --git a/examples/tcp/Makefile b/examples/tcp/Makefile index 035017a6f7..4e9eb8ef0c 100644 --- a/examples/tcp/Makefile +++ b/examples/tcp/Makefile @@ -4,133 +4,21 @@ # The name of your service SERVICE = tcp_demo +SERVICE_NAME = IncludeOS TCP Demo # Your service parts FILES = service.cpp -# IncludeOS location -ifndef INCLUDEOS_INSTALL - INCLUDEOS_INSTALL=$(HOME)/IncludeOS_install -endif - -# Shorter name -INSTALL = $(INCLUDEOS_INSTALL) - -# Compiler/Linker -################################################### - -OPTIONS = -Ofast -msse3 -Wall -Wextra -mstackrealign - -# External Libraries -################################################### -LIBC_OBJ = $(INSTALL)/newlib/libc.a -LIBG_OBJ = $(INSTALL)/newlib/libg.a -LIBM_OBJ = $(INSTALL)/newlib/libm.a - -LIBGCC = $(INSTALL)/libgcc/libgcc.a -LIBCXX = $(INSTALL)/libcxx/libc++.a $(INSTALL)/libcxx/libc++abi.a - - -INC_NEWLIB=$(INSTALL)/newlib/include -INC_LIBCXX=$(INSTALL)/libcxx/include +# Your disk image +DISK= -DEBUG_OPTS = -ggdb3 -v +# Your own include-path +LOCAL_INCLUDES= -CPP = clang++-3.6 -target i686-elf -ifndef LD_INC - LD_INC = ld +# IncludeOS location +ifndef INCLUDEOS_INSTALL +INCLUDEOS_INSTALL=$(HOME)/IncludeOS_install endif -INCLUDES = -I$(INC_LIBCXX) -I$(INSTALL)/api/sys -I$(INC_NEWLIB) -I$(INSTALL)/api - -CAPABS_COMMON = -msse3 -mstackrealign # Needed for 16-byte stack alignment (SSE) - -all: CAPABS = $(CAPABS_COMMON) -O2 -debug: CAPABS = $(CAPABS_COMMON) -O0 -stripped: CAPABS = $(CAPABS_COMMON) -Oz - -WARNS = -Wall -Wextra #-pedantic -CPPOPTS = $(CAPABS) $(WARNS) -c -m32 -std=c++14 -fno-stack-protector $(INCLUDES) -D_LIBCPP_HAS_NO_THREADS=1 #-flto -fno-exceptions - -LDOPTS = -nostdlib -melf_i386 -N --script=$(INSTALL)/linker.ld -flto - - -# Objects -################################################### - -CRTBEGIN_OBJ = $(INSTALL)/crt/crtbegin.o -CRTEND_OBJ = $(INSTALL)/crt/crtend.o -CRTI_OBJ = $(INSTALL)/crt/crti.o -CRTN_OBJ = $(INSTALL)/crt/crtn.o - -# Full link list -OBJS = $(FILES:.cpp=.o) .service_name.o -LIBS = $(INSTALL)/os.a $(LIBCXX) $(INSTALL)/os.a $(LIBC_OBJ) $(LIBM_OBJ) $(LIBGCC) - -OS_PRE = $(CRTBEGIN_OBJ) $(CRTI_OBJ) -OS_POST = $(CRTEND_OBJ) $(CRTN_OBJ) - -DEPS = $(OBJS:.o=.d) - -# Complete bulid -################################################### -# A complete build includes: -# - a "service", to be linked with OS-objects (OS included) - -all: service - -stripped: LDOPTS += -S #strip all -stripped: CPPOPTS += -Oz -stripped: service - - -# The same, but with debugging symbols (OBS: Dramatically increases binary size) -debug: CCOPTS += $(DEBUG_OPTS) -debug: CPPOPTS += $(DEBUG_OPTS) -debug: LDOPTS += -M --verbose - -debug: OBJS += $(LIBG_OBJ) - -debug: service #Don't wanna call 'all', since it strips debug info - -# Service -################################################### -service.o: service.cpp - @echo "\n>> Compiling the service" - $(CPP) $(CPPOPTS) -o $@ $< - -.service_name.o: $(INSTALL)/service_name.cpp - $(CPP) $(CPPOPTS) -DSERVICE_NAME="\"$(SERVICE)\"" -o $@ $< - -# Link the service with the os -service: $(OBJS) $(LIBS) - @echo "\n>> Linking service with OS" - $(LD_INC) $(LDOPTS) $(OS_PRE) $(OBJS) $(LIBS) $(OS_POST) -o $(SERVICE) - @echo "\n>> Building image " $(SERVICE).img - $(INSTALL)/vmbuild $(INSTALL)/bootloader $(SERVICE) - -# Object files -################################################### - -# Runtime -crt%.o: $(INSTALL)/crt/crt%.s - @echo "\n>> Assembling C runtime:" $@ - $(CPP) $(CPPOPTS) -x assembler-with-cpp $< - -# General C++-files to object files -%.o: %.cpp - @echo "\n>> Compiling OS object without header" - $(CPP) $(CPPOPTS) -o $@ $< - -# AS-assembled object files -%.o: %.s - @echo "\n>> Assembling GNU 'as' files" - $(CPP) $(CPPOPTS) -x assembler-with-cpp $< - -# Cleanup -################################################### -clean: - $(RM) $(OBJS) $(DEPS) $(SERVICE) - $(RM) $(SERVICE).img - --include $(DEPS) +# Include the installed seed makefile +include $(INCLUDEOS_INSTALL)/Makeseed diff --git a/examples/tcp/debug/service.gdb b/examples/tcp/debug/service.gdb deleted file mode 100644 index f5acb4ff3c..0000000000 --- a/examples/tcp/debug/service.gdb +++ /dev/null @@ -1,5 +0,0 @@ -file service -break _start -break OS::start -set non-stop off -target remote localhost:1234 \ No newline at end of file diff --git a/examples/tcp/service.cpp b/examples/tcp/service.cpp index 48dc5a754c..bbed1ce741 100644 --- a/examples/tcp/service.cpp +++ b/examples/tcp/service.cpp @@ -6,9 +6,9 @@ // 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. @@ -16,22 +16,23 @@ // limitations under the License. /* - An example to show incoming and outgoing TCP Connections. - In this example, IncludeOS is listening on port 80. - - Data received on port 80 will be redirected to a - outgoing connection to a (in this case) python server (server.py) - - Data received from the python server connection - will be redirected back to the client. - - To try it out, use netcat to connect to this IncludeOS instance. + An example to show incoming and outgoing TCP Connections. + In this example, IncludeOS is listening on port 80. + + Data received on port 80 will be redirected to a + outgoing connection to a (in this case) python server (server.py) + + Data received from the python server connection + will be redirected back to the client. + + To try it out, use netcat to connect to this IncludeOS instance. */ #include #include using Connection_ptr = std::shared_ptr; +using Disconnect = net::TCP::Connection::Disconnect; // An IP-stack object std::unique_ptr > inet; @@ -41,73 +42,71 @@ std::unique_ptr > inet; net::TCP::Socket python_server{ {{10,0,2,2}} , 1337}; // Called when data is received on client (incoming connection) -void handle_client_on_receive(Connection_ptr client, Connection_ptr python) { - // Read the request from our client - std::string request = client->read(1024); - printf("Received [Client]: %s\n", request.c_str()); - // Write the request to our python server - python->write(request); +void handle_client_on_read(Connection_ptr python, std::string request) { + printf("Received [Client]: %s\n", request.c_str()); + // Write the request to our python server + python->write(request.data(), request.size()); } // Called when data is received on python (outgoing connection) -void handle_python_on_receive(Connection_ptr python, Connection_ptr client) { - // Read the response from our python server - std::string response = python->read(1024); - // Write response to our client - client->write(response); +void handle_python_on_read(Connection_ptr client, std::string response) { + // Write response to our client + client->write(response.data(), response.size()); } void Service::start() { // Assign a driver (VirtioNet) to a network interface (eth0) hw::Nic& eth0 = hw::Dev::eth<0,VirtioNet>(); - + // Bring up a network stack, attached to the nic inet = std::make_unique >(eth0); - + // Static IP configuration, until we (possibly) get DHCP inet->network_config( {{ 10,0,0,42 }}, // IP - {{ 255,255,255,0 }}, // Netmask - {{ 10,0,0,1 }}, // Gateway - {{ 8,8,8,8 }} ); // DNS + {{ 255,255,255,0 }}, // Netmask + {{ 10,0,0,1 }}, // Gateway + {{ 8,8,8,8 }} ); // DNS // Set up a TCP server on port 80 auto& server = inet->tcp().bind(80); inet->dhclient()->on_config([&server](auto&) { - printf("Server IP updated: %s\n", server.local().to_string().c_str()); - }); + printf("Server IP updated: %s\n", server.local().to_string().c_str()); + }); printf("Server listening: %s \n", server.local().to_string().c_str()); // When someone connects to our server server.onConnect([](Connection_ptr client) { - printf("Connected [Client]: %s\n", client->to_string().c_str()); - // Make an outgoing connection to our python server - auto outgoing = inet->tcp().connect(python_server); - // When outgoing connection to python sever is established - outgoing->onConnect([client](Connection_ptr python) { - printf("Connected [Python]: %s\n", python->to_string().c_str()); - - // Setup handlers for when data is received on client and python connection - // When client has data to be read - client->onReceive([python](Connection_ptr client, bool) { - handle_client_on_receive(client, python); - }); - - // When python server has data to be read - python->onReceive([client](Connection_ptr python, bool) { - handle_python_on_receive(python, client); - }); - - // When client is disconnecting - client->onDisconnect([python](Connection_ptr, std::string msg) { - printf("Disconnected [Client]: %s\n", msg.c_str()); - python->close(); - }); - - // When python is disconnecting - python->onDisconnect([client](Connection_ptr, std::string msg) { - printf("Disconnected [Python]: %s\n", msg.c_str()); - client->close(); - }); - }); // << onConnect (outgoing (python)) - }); // << onConnect (client) + printf("Connected [Client]: %s\n", client->to_string().c_str()); + // Make an outgoing connection to our python server + auto outgoing = inet->tcp().connect(python_server); + // When outgoing connection to python sever is established + outgoing->onConnect([client](Connection_ptr python) { + printf("Connected [Python]: %s\n", python->to_string().c_str()); + + // Setup handlers for when data is received on client and python connection + // When client reads data + client->read(1024, [python](auto buf, size_t n) { + std::string data{ (char*)buf.get(), n }; + handle_client_on_read(python, data); + }); + + // When python server reads data + python->read(1024, [client](auto buf, size_t n) { + std::string data{ (char*)buf.get(), n }; + handle_python_on_read(client, data); + }); + + // When client is disconnecting + client->onDisconnect([python](Connection_ptr, Disconnect reason) { + printf("Disconnected [Client]: %s\n", reason.to_string().c_str()); + python->close(); + }); + + // When python is disconnecting + python->onDisconnect([client](Connection_ptr, Disconnect reason) { + printf("Disconnected [Python]: %s\n", reason.to_string().c_str()); + client->close(); + }); + }); // << onConnect (outgoing (python)) + }); // << onConnect (client) } diff --git a/install.sh b/install.sh index 485d0d8e11..9aea62f534 100755 --- a/install.sh +++ b/install.sh @@ -13,16 +13,16 @@ export build_dir=$HOME/cross-dev # Multitask-parameter to make export num_jobs=-j$((`lscpu -p | tail -1 | cut -d',' -f1` + 1 )) -export newlib_version=2.2.0-1 +export newlib_version=2.4.0 export INCLUDEOS_SRC=`pwd` export newlib_inc=$TEMP_INSTALL_DIR/i686-elf/include export llvm_src=llvm export llvm_build=build_llvm -export clang_version=3.6 +export clang_version=3.8 -export gcc_version=5.1.0 -export binutils_version=2.25 +export gcc_version=6.1.0 +export binutils_version=2.26 # Options to skip steps [ ! -v do_binutils ] && do_binutils=1 @@ -35,14 +35,15 @@ export binutils_version=2.25 [ ! -v install_llvm_dependencies ] && export install_llvm_dependencies=1 [ ! -v download_llvm ] && export download_llvm=1 - +$INCLUDEOS_SRC/etc/prepare_ubuntu_deps.sh # BUILDING IncludeOS -PREREQS_BUILD="build-essential make nasm texinfo clang-$clang_version clang++-$clang_version" +DEPS_BUILD="build-essential make nasm texinfo clang-$clang_version clang++-$clang_version" + echo -e "\n\n >>> Trying to install prerequisites for *building* IncludeOS" -echo -e " Packages: $PREREQS_BUILD \n" -sudo apt-get install -y $PREREQS_BUILD +echo -e " Packages: $DEPS_BUILD \n" +sudo apt-get install -y $DEPS_BUILD mkdir -p $BUILD_DIR cd $BUILD_DIR @@ -65,45 +66,46 @@ fi if [ ! -z $do_llvm ]; then echo -e "\n\n >>> GETTING / BUILDING llvm / libc++ \n" $INCLUDEOS_SRC/etc/build_llvm32.sh - #echo -e "\n\n >>> INSTALLING libc++ \n" - #cp $BUILD_DIR/$llvm_build/lib/libc++.a $INSTALL_DIR/lib/ fi echo -e "\n >>> DEPENDENCIES SUCCESSFULLY BUILT. Creating binary bundle \n" $INCLUDEOS_SRC/etc/create_binary_bundle.sh +echo -e "\n\n>>> Installing submodules" +pushd $INCLUDEOS_SRC +git submodule init +git submodule update +popd if [ ! -z $do_includeos ]; then - # Build and install the vmbuilder + # Build and install the vmbuilder echo -e "\n >>> Installing vmbuilder" pushd $INCLUDEOS_SRC/vmbuild - make + make cp vmbuild $INSTALL_DIR/ popd - + echo -e "\n >>> Building IncludeOS" pushd $INCLUDEOS_SRC/src make $num_jobs - + echo -e "\n >>> Linking IncludeOS test-service" make test - + echo -e "\n >>> Installing IncludeOS" make install - popd - - + # RUNNING IncludeOS - PREREQS_RUN="bridge-utils qemu-kvm" + DEPS_RUN="bridge-utils qemu-kvm" echo -e "\n\n >>> Trying to install prerequisites for *running* IncludeOS" - echo -e " Packages: $PREREQS_RUN \n" - sudo apt-get install -y $PREREQS_RUN - + echo -e " Packages: $DEPS_RUN \n" + sudo apt-get install -y $DEPS_RUN + # Set up the IncludeOS network bridge echo -e "\n\n >>> Create IncludeOS network bridge *Requires sudo* \n" sudo $INCLUDEOS_SRC/etc/create_bridge.sh - + # Copy qemu-ifup til install loc. $INCLUDEOS_SRC/etc/copy_scripts.sh fi diff --git a/mod/GSL b/mod/GSL index 85ffc8d222..a9f865900d 160000 --- a/mod/GSL +++ b/mod/GSL @@ -1 +1 @@ -Subproject commit 85ffc8d222957ca7d6d30fb75fa61fe1e01fa1d4 +Subproject commit a9f865900d28b854de5ead971aadb82e5ef9ed40 diff --git a/src/Makefile b/src/Makefile index 0cbbebcc7e..fd93c32991 100644 --- a/src/Makefile +++ b/src/Makefile @@ -9,10 +9,16 @@ endif # shorter name INSTALL = $(INCLUDEOS_INSTALL) +STACK_PROTECTOR_VALUE=$(shell awk 'BEGIN{print int(rand()*65536)}') # stackrealign is needed to guarantee 16-byte stack alignment for SSE # the compiler seems to be really dumb in this regard, creating a misaligned stack left and right -CAPABS_COMMON = -mstackrealign -msse3 -CAPABS = $(CAPABS_COMMON) -O2 -DNO_DEBUG=1 +CAPABS_COMMON = -mstackrealign -fstack-protector-all -fno-omit-frame-pointer -msse3 -D_STACK_GUARD_VALUE_=$(STACK_PROTECTOR_VALUE) + +# Various global defines +# * NO_DEBUG Disables output from the debug macro +# * OS_TERMINATE_ON_CONTRACT_VIOLATION provides classic assert-like output from Expects / Ensures +# * _GNU_SOURCE enables POSIX-extensions in newlib, such as strnlen. ("everything newlib has", ref. cdefs.h) +CAPABS = $(CAPABS_COMMON) -O2 -DNO_DEBUG=1 -DOS_TERMINATE_ON_CONTRACT_VIOLATION -D_GNU_SOURCE WARNS = -Wall -Wextra #-pedantic DEBUG_OPTS = -ggdb3 @@ -21,7 +27,7 @@ DEBUG_OPTS = -ggdb3 ################################################### LIBC_OBJ = $(INSTALL)/newlib/libc.a LIBG_OBJ = $(INSTALL)/newlib/libg.a -LIBM_OBJ = $(INSTALL)/newlib/libm.a +LIBM_OBJ = $(INSTALL)/newlib/libm.a LIBGCC = $(INSTALL)/libgcc/libgcc.a LIBCXX = $(INSTALL)/libcxx/libc++.a libc++abi.a @@ -31,8 +37,8 @@ INC_NEWLIB=$(INSTALL)/newlib/include # Compiler/Linker ################################################### -CC = clang-3.6 -CPP = clang++-3.6 +CC = clang-3.8 +CPP = clang++-3.8 # Set defaults if not defined ifndef LD_INC LD_INC = ld @@ -42,38 +48,40 @@ ifndef AR_INC endif # Compiler options -CCOPTS = -target i686-elf -v +CCOPTS = -target i686-elf CPPOPTS = -target i686-elf -INCLUDES = -I../api/sys -I$(INSTALL)/libcxx/include -I$(INC_NEWLIB) -Iinclude -I../api # +INCLUDES = -I../api/sys -I$(INSTALL)/libcxx/include -I$(INC_NEWLIB) -Iinclude -I../api -I../mod/GSL/include # CINCLUDES = -I../api/sys -I$(INC_NEWLIB) -Iinclude -I../api # -CCOPTS += $(CAPABS) $(WARNS) -c -m32 -fno-stack-protector -fno-builtin -march=i686 $(CINCLUDES) -CPPOPTS += $(CAPABS) $(WARNS) -c -m32 -std=c++14 -fno-stack-protector $(INCLUDES) -D_LIBCPP_HAS_NO_THREADS=1 -DOS_VERSION="\"$(shell git describe --dirty)\"" +CCOPTS += $(CAPABS) $(WARNS) -c -m32 -march=i686 $(CINCLUDES) +CPPOPTS += $(CAPABS) $(WARNS) -c -m32 -std=c++14 $(INCLUDES) -D_LIBCPP_HAS_NO_THREADS=1 -DOS_VERSION="\"$(shell git describe --dirty)\"" -LDOPTS = -nostdlib -melf_i386 -N --eh-frame-hdr --script=linker.ld #-flto +LDOPTS = -nostdlib -melf_i386 -N --eh-frame-hdr --script=linker.ld #-flto # Objects ################################################### CXXABI = $(shell find ./crt/cxxabi -type f -name "*.cpp") CXXABI_OBJ = $(CXXABI:.cpp=.o) -OS_OBJECTS = kernel/kernel_start.o kernel/syscalls.o kernel/vga.o \ +OS_OBJECTS = kernel/kernel_start.o kernel/syscalls.o \ kernel/interrupts.o kernel/os.o kernel/cpuid.o \ kernel/irq_manager.o kernel/pci_manager.o \ + kernel/terminal.o kernel/terminal_disk.o \ + kernel/vga.o util/memstream.o util/async.o \ crt/c_abi.o crt/string.o crt/quick_exit.o crt/cxx_abi.o crt/mman.o \ - util/memstream.o \ hw/ide.o hw/pit.o hw/pic.o hw/pci_device.o hw/cpu_freq_sampling.o \ + hw/serial.o hw/apic.o hw/apic_asm.o hw/cmos.o\ virtio/virtio.o virtio/virtio_queue.o virtio/virtionet.o \ - virtio/block.o virtio/console.o \ - net/ethernet.o net/inet_common.o net/arp.o net/ip4.o \ + net/ethernet.o net/inet_common.o net/ip4/arp.o net/ip4/ip4.o \ net/tcp.o net/tcp_connection.o net/tcp_connection_states.o \ net/ip4/icmpv4.o net/ip4/udp.o net/ip4/udp_socket.o \ net/dns/dns.o net/dns/client.o net/dhcp/dh4client.o \ net/ip6/ip6.o net/ip6/icmp6.o net/ip6/udp6.o net/ip6/ndp.o \ net/packet.o net/buffer_store.o \ - fs/filesystem.o fs/mbr.o fs/vbr.o fs/path.o \ - fs/ext4.o fs/fat.o fs/fat_sync.o fs/memdisk.o + fs/disk.o fs/filesystem.o fs/mbr.o fs/path.o \ + fs/ext4.o fs/fat.o fs/fat_async.o fs/fat_sync.o fs/memdisk.o \ + virtio/block.o # virtio/console.o CRTI_OBJ = crt/crti.o CRTN_OBJ = crt/crtn.o @@ -82,6 +90,7 @@ CRTN_OBJ = crt/crtn.o ################################################### OS_DEPS = $(OS_OBJECTS:.o=.d) +.PHONY: memdisk all debug stripped clean # Complete OS build ################################################### @@ -89,20 +98,34 @@ OS_DEPS = $(OS_OBJECTS:.o=.d) # - a bootloader # - an OS library for the service to link against -all: bootloader libc++abi.a os.a +all: bootloader libc++abi.a os.a @echo "\n>>> Built OS-library. Install to '"$(INSTALL)"' using 'make install'" #stripped: LDOPTS += -s #strip all stripped: CAPABS += -Oz stripped: all test -# The same, but with debugging symbols (OBS: Dramatically increases binary size) +# Build like "all" but with debugging output (i.e. the 'debug'-macro) enabled +debug-info: CAPABS += -UNO_DEBUG +debug-info: CAPABS += -DGSL_THROW_ON_CONTRACT_VIOLATION +debug-info: all test + +# Build with debugging symbols (OBS: Dramatically increases binary size) debug: CCOPTS += $(DEBUG_OPTS) debug: CPPOPTS += $(DEBUG_OPTS) debug: OBJ_LIST += $(LIBG_OBJ) -debug: CAPABS += -O0 -UNO_DEBUG +debug: CAPABS += -O0 debug: all test +# Build with debugging symbols + debugging ouput, i.e. "debug" + "debug-info" +debug-all: CAPABS += -UNO_DEBUG +debug-all: CCOPTS += $(DEBUG_OPTS) +debug-all: CPPOPTS += $(DEBUG_OPTS) +debug-all: OBJ_LIST += $(LIBG_OBJ) +debug-all: CAPABS += -O0 +debug-all: all test + + silent: CPPOPTS += -DNO_INFO=1 silent: all test @@ -114,11 +137,12 @@ LIBS_OBJ = os.a $(LIBCXX) os.a $(LIBC_OBJ) $(LIBM_OBJ) $(LIBGCC) CRTBEGIN_OBJ = $(CRTI_OBJ) $(INSTALL)/crt/crtbegin.o CRTEND_OBJ = $(INSTALL)/crt/crtend.o $(CRTN_OBJ) -TEST_OBJ = debug/test_service.o debug/ircd.o util/service_name.o +TEST_OBJ = debug/test_service.o util/service_name.o test_service: CPPOPTS += -DSERVICE_NAME="\"Test Service\"" test_service: $(TEST_OBJ) $(CRTI_OBJ) $(CRTN_OBJ) @echo "\n>> Linking test service" + touch smalldisk $(LD_INC) -v $(LDOPTS) $(CRTBEGIN_OBJ) $(TEST_OBJ) $(LIBS_OBJ) $(CRTEND_OBJ) -o debug/$@ test_ipv6: CPPOPTS += -DSERVICE_NAME="\"IPv6 Test Service\"" @@ -129,6 +153,7 @@ test_ipv6: debug/test_ipv6.o $(CRTI_OBJ) $(CRTN_OBJ) test_disk: CPPOPTS += -DSERVICE_NAME="\"Virtio-disk Test\"" test_disk: debug/test_disk.o $(CRTI_OBJ) $(CRTN_OBJ) memdisk @echo "\n>> Linking disk test" + touch smalldisk $(LD_INC) -v $(LDOPTS) $(CRTBEGIN_OBJ) debug/test_disk.o $(LIBS_OBJ) $(CRTEND_OBJ) memdisk.o -o debug/$@ test_tcp: CPPOPTS += -DSERVICE_NAME="\"TCP Test Service\"" @@ -163,22 +188,28 @@ install: $(CRTI_OBJ) $(CRTN_OBJ) # Seed related cp util/service_name.cpp $(INSTALL) cp seed/Makefile $(INSTALL)/Makeseed + # Eexternal dependencies related + mkdir -p $(INSTALL)/mod/ + cp -r ../mod/GSL $(INSTALL)/mod @echo "\nDone!" # Makefile recipes ################################################### %.o: %.c - $(CC) -MMD $(CCOPTS) -o $@ $< + $(CC) -MMD $(CCOPTS) -o $@ $< -%.o: %.cpp +%.o: %.cpp @echo "\n" - $(CPP) -MMD $(CPPOPTS) -o $@ $< + $(CPP) -MMD $(CPPOPTS) -o $@ $< # AS-assembled object files %.o: %.s @echo "\n" $(CPP) $(CPPOPTS) -x assembler-with-cpp $< -o $@ +%.o: %.asm + nasm -f elf $< -o $@ + # Bootloader ################################################### bootloader: boot/bootloader.asm boot/disk_read_lba.asm diff --git a/src/boot/bootloader.asm b/src/boot/bootloader.asm index 9fc68ae4f0..f0cdf5fe9d 100644 --- a/src/boot/bootloader.asm +++ b/src/boot/bootloader.asm @@ -16,26 +16,27 @@ ;; limitations under the License. USE16 - ;; Memory layout, 16-bit - %define _boot_segment 0x7c0 +;; Memory layout, 16-bit +%define _boot_segment 0x07c0 +%define _vga_segment 0xb800 + +;; Memory layout, 32-bit +%define _mode32_code_segment 0x08 +%define _mode32_data_segment 0x10 - ;; Memory layout, 32-bit - %define _mode32_code_segment 0x08 - %define _mode32_data_segment 0x10 +%define _kernel_loc 0x200000 +%define _kernel_stack 0x200000 - %define _kernel_loc 0x200000 - %define _kernel_stack 0x200000 - - ;; We don't really need a stack, except for calls - %define _stack_segment 0x7000 - %define _stack_pointer 0xfffe ;Within the ss, for 16 bit +;; We don't really need a stack, except for calls +%define _stack_segment 0x7000 +%define _stack_pointer 0xfffe ;Within the ss, for 16 bit - ;; Char helpers - %define _CR 0x0D - %define _LF 0x0A +;; Char helpers +%define _CR 0x0D +%define _LF 0x0A - ;; ELF offset of _start in text section - %define _elf_start 0x34 +; ELF offset of _start in text section +%define _elf_start 0x34 ;;; START ;;; We'll start at the beginning, but jump over some data @@ -45,40 +46,42 @@ _start: ;;; The size of the service on disk, to be loaded at boot. (Convenient to ;;; have it at the beginning, so it remains at a fixed location. ) srv_size: - dd 0 + dd 0 srv_offs: dd 0 ;;; Actual start boot: - ;; Need to set data segment, to access strings mov ax, _boot_segment mov ds, ax + + ; fast a20 enable + in al, 0x92 + or al, 2 + out 0x92, al + + ;; Clear the screen + call fill_screen - mov esi,str_boot + ;; Print the IncludeOS logo + mov esi, str_includeos call printstr - - ; fast a20 enable - in al, 0x92 - or al, 2 - out 0x92, al + mov ax, 0x07 + mov [color], ax + mov esi, str_literally + call printstr + ; check that it was enabled test al,0 - jz .a20_ok - + jz .a20_ok ;; NOT OK mov esi,str_a20_fail call printstr cli hlt .a20_ok: - mov esi,str_a20_ok - call printstr - - - call protected_mode protected_mode: @@ -90,49 +93,50 @@ protected_mode: mov eax,cr0 or al,1 mov cr0,eax - - ;xchg bx,bx - ;; jmp 0x8:$+3 jmp _mode32_code_segment:mode32+(_boot_segment<<4) -USE16 - - ;; %include "asm/load_os_16bit.asm" - -;;; 16-bit code -USE16 +fill_screen: + mov bx, _vga_segment + mov es, bx + mov bx, 0 + mov al, 0 + mov ah, [color] +.fill: + mov [es:bx], ax + add bx,2 + cmp bx, (25*80*2) + jge .done + jmp .fill +.done: + ret printstr: - mov ah,0eh -.repeat: + mov bx, _vga_segment + mov es, bx + mov bx, [cursor] + mov ah, [color] +.repeat: lodsb cmp al,0 je .done - int 10h + mov [es:bx], al + mov [es:bx + 1], ah + add bx, 2 jmp .repeat .done: + mov [cursor], bx ret -;;; Print the char in %al to serial port, via BIOS int. -;;; OBS: Only works in real mode. -;;; OBS: In Bochs, looks like serial port must be initialized first -print_al_serial: - xor edx,edx - mov ah,1 - int 14h - ret - -print_al_scr: - mov ah,0x0e - int 0x10 - -str_boot: - db `IncludeOS!\n\r`,0 -str_a20_ok: - db `A20 OK!\n\r`,0 -str_a20_fail: - db `A20 NOT OK\n\r`,0 - +str_includeos: + db `#include `,0 +str_literally: + db `\/\/ Literally `,0 +str_a20_fail: + db `A20 Err\n\r`,0 +cursor: + dw (80 * 11 * 2) + 48 +color: + dw 0x0d USE32 ALIGN 32 @@ -206,16 +210,16 @@ mode32: cmp edx, 0 jge .more ;; jump when gequal - ;; Bochs breakpoint + ;; Bochs breakpoint ;;xchg bx,bx ;; GERONIMO! ;; Jump to service mov ecx, [srv_offs+(_boot_segment<<4)] - jmp ecx + jmp ecx %include "boot/disk_read_lba.asm" -;; BOOT SIGNATURE + ;; BOOT SIGNATURE times 510-($-$$) db 0 ; dw 0xAA55 diff --git a/src/crt/c_abi.c b/src/crt/c_abi.c index 9c42bff08b..f3a856af89 100644 --- a/src/crt/c_abi.c +++ b/src/crt/c_abi.c @@ -34,6 +34,10 @@ __FILE* stdin; __FILE* stdout; __FILE* stderr; +// stack-protector guard +uintptr_t __stack_chk_guard = _STACK_GUARD_VALUE_; +extern void panic(const char* why) __attribute__((noreturn)); + void _init_c_runtime() { // Initialize .bss section @@ -44,8 +48,8 @@ void _init_c_runtime() extern caddr_t heap_end; // used by SBRK: extern char _end; // Defined by the linker // Set heap to after _end (given by linker script) if needed - if (&_end > heap_end) - heap_end = &_end; + // note: end should be aligned to next page by linker + heap_end = &_end; /// initialize newlib I/O newlib_reent = (struct _reent) _REENT_INIT(newlib_reent); @@ -70,6 +74,13 @@ void _init_c_runtime() // global/static objects should never be destructed here, so ignore this void* __dso_handle; +// stack-protector +__attribute__((noreturn)) +void __stack_chk_fail(void) +{ + panic("Stack protector: Canary modified"); +} + // old function result system int errno = 0; int* __errno_location(void) diff --git a/src/crt/cxx_abi.cpp b/src/crt/cxx_abi.cpp index 911ead7b00..40617afe7f 100644 --- a/src/crt/cxx_abi.cpp +++ b/src/crt/cxx_abi.cpp @@ -22,7 +22,7 @@ * This header is for instantiating and implementing * missing functionality gluing libc++ to the kernel * -**/ + **/ #include extern "C" diff --git a/src/crt/cxxabi/cxa_guard.cpp b/src/crt/cxxabi/cxa_guard.cpp index 1b4191713e..6e447de3c1 100644 --- a/src/crt/cxxabi/cxa_guard.cpp +++ b/src/crt/cxxabi/cxa_guard.cpp @@ -28,6 +28,8 @@ namespace __cxxabiv1 { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" namespace { @@ -162,6 +164,7 @@ set_lock(uint32_t& x, lock_type y) #endif // __APPLE__ } // unnamed namespace +#pragma GCC diagnostic pop extern "C" { diff --git a/src/crt/mman.cpp b/src/crt/mman.cpp index d82a15511d..8baadf17fa 100644 --- a/src/crt/mman.cpp +++ b/src/crt/mman.cpp @@ -20,15 +20,15 @@ struct mmap_entry_t std::map _mmap_entries; void* mmap(void* addr, size_t length, - int prot, int flags, - int fd, off_t offset) + int prot, int flags, + int fd, off_t offset) { // invalid or misaligned length if (length == 0 || (length & 4095) != 0) - { - errno = EINVAL; - return MAP_FAILED; - } + { + errno = EINVAL; + return MAP_FAILED; + } // associate some VA space with open file @fd // for now just allocate page-aligned on heap @@ -53,15 +53,15 @@ int munmap(void* addr, size_t length) { auto it = _mmap_entries.find(addr); if (it != _mmap_entries.end()) - { - // - assert(it->second.length == length); + { + // + assert(it->second.length == length); - // free and remove the entry - free(it->second.addr); - _mmap_entries.erase(it); - return 0; - } + // free and remove the entry + free(it->second.addr); + _mmap_entries.erase(it); + return 0; + } errno = EINVAL; return -1; } diff --git a/src/debug/ircd.cpp b/src/debug/ircd.cpp index f4ca803615..b42042a0b6 100644 --- a/src/debug/ircd.cpp +++ b/src/debug/ircd.cpp @@ -12,9 +12,9 @@ void Client::split_message(const std::string& msg) printf("[Client]: "); for (auto& str : vec) - { - printf("[%s]", str.c_str()); - } + { + printf("[%s]", str.c_str()); + } printf("\n"); // ignore empty messages if (vec.size() == 0) return; @@ -25,47 +25,47 @@ void Client::split_message(const std::string& msg) void Client::read(const char* buf, size_t len) { while (len > 0) - { - int search = -1; - - for (size_t i = 0; i < len; i++) - if (buf[i] == 13 || buf[i] == 10) - { - search = i; break; - } - // not found: - if (search == -1) - { - // append entire buffer - buffer.append(buf, len); - break; - } - else { - // found CR LF: - if (search != 0) - { - // append to clients buffer - buffer.append(buf, search); - - // move forward in socket buffer - buf += search; - // decrease len - len -= search; - } + int search = -1; + + for (size_t i = 0; i < len; i++) + if (buf[i] == 13 || buf[i] == 10) + { + search = i; break; + } + // not found: + if (search == -1) + { + // append entire buffer + buffer.append(buf, len); + break; + } else - { - buf++; len--; - } + { + // found CR LF: + if (search != 0) + { + // append to clients buffer + buffer.append(buf, search); + + // move forward in socket buffer + buf += search; + // decrease len + len -= search; + } + else + { + buf++; len--; + } - // parse message - if (buffer.size()) - { - split_message(buffer); - buffer.clear(); - } + // parse message + if (buffer.size()) + { + split_message(buffer); + buffer.clear(); + } + } } - } } void Client::send(uint16_t numeric, std::string text) @@ -94,59 +94,59 @@ void Client::send(std::string text) void Client::handle(const std::string&, const std::vector& msg) { - #define TK_CAP "CAP" - #define TK_PASS "PASS" - #define TK_NICK "NICK" - #define TK_USER "USER" +#define TK_CAP "CAP" +#define TK_PASS "PASS" +#define TK_NICK "NICK" +#define TK_USER "USER" const std::string& cmd = msg[0]; if (this->is_reg() == false) - { - if (cmd == TK_CAP) - { - // ignored completely - } - else if (cmd == TK_PASS) { - if (msg.size() > 1) - { - this->passw = msg[1]; - } + if (cmd == TK_CAP) + { + // ignored completely + } + else if (cmd == TK_PASS) + { + if (msg.size() > 1) + { + this->passw = msg[1]; + } + else + { + send(ERR_NEEDMOREPARAMS, cmd + " :Not enough parameters"); + } + } + else if (cmd == TK_NICK) + { + if (msg.size() > 1) + { + this->nick = msg[1]; + welcome(regis | 1); + } + else + { + send(ERR_NEEDMOREPARAMS, cmd + " :Not enough parameters"); + } + } + else if (cmd == TK_USER) + { + if (msg.size() > 1) + { + this->user = msg[1]; + welcome(regis | 2); + } + else + { + send(ERR_NEEDMOREPARAMS, cmd + " :Not enough parameters"); + } + } else - { - send(ERR_NEEDMOREPARAMS, cmd + " :Not enough parameters"); - } + { + send(ERR_NOSUCHCMD, cmd + " :Unknown command"); + } } - else if (cmd == TK_NICK) - { - if (msg.size() > 1) - { - this->nick = msg[1]; - welcome(regis | 1); - } - else - { - send(ERR_NEEDMOREPARAMS, cmd + " :Not enough parameters"); - } - } - else if (cmd == TK_USER) - { - if (msg.size() > 1) - { - this->user = msg[1]; - welcome(regis | 2); - } - else - { - send(ERR_NEEDMOREPARAMS, cmd + " :Not enough parameters"); - } - } - else - { - send(ERR_NOSUCHCMD, cmd + " :Unknown command"); - } - } } #define RPL_WELCOME 1 @@ -162,15 +162,15 @@ void Client::welcome(uint8_t newreg) regis = newreg; // not registered before, but registered now if (!regged && is_reg()) - { - printf("* Registered: %s\n", nickuserhost().c_str()); - send(RPL_WELCOME, ":Welcome to the Internet Relay Network, " + nickuserhost()); - send(RPL_YOURHOST, ":Your host is " + SERVER_NAME + ", running v1.0"); - } + { + printf("* Registered: %s\n", nickuserhost().c_str()); + send(RPL_WELCOME, ":Welcome to the Internet Relay Network, " + nickuserhost()); + send(RPL_YOURHOST, ":Your host is " + SERVER_NAME + ", running v1.0"); + } else if (oldreg == 0) - { - auth_notice(); - } + { + auth_notice(); + } } void Client::auth_notice() { diff --git a/src/debug/ircsplit.hpp b/src/debug/ircsplit.hpp index f9a572a962..1f80098869 100644 --- a/src/debug/ircsplit.hpp +++ b/src/debug/ircsplit.hpp @@ -10,40 +10,40 @@ split(const std::string& text, std::string& source) if (text.empty()) return retv; // check for source if (text[0] == ':') - { - x = text.find(" ", 1); - source = text.substr(x); - // early return for source-only msg - if (x == std::string::npos) return retv; - p = x+1; - } - // parse remainder - do - { - x = text.find(" ", p+1); - size_t y = text.find(":", x+1); // find last param - - if (y == x+1) - { - // single argument - retv.push_back(text.substr(p, x-p)); - // ending text argument - retv.push_back(text.substr(y+1)); - break; - } - else if (x != std::string::npos) { - // single argument - retv.push_back(text.substr(p, x-p)); + x = text.find(" ", 1); + source = text.substr(x); + // early return for source-only msg + if (x == std::string::npos) return retv; + p = x+1; } - else + // parse remainder + do { - // last argument - retv.push_back(text.substr(p)); - } - p = x+1; + x = text.find(" ", p+1); + size_t y = text.find(":", x+1); // find last param + + if (y == x+1) + { + // single argument + retv.push_back(text.substr(p, x-p)); + // ending text argument + retv.push_back(text.substr(y+1)); + break; + } + else if (x != std::string::npos) + { + // single argument + retv.push_back(text.substr(p, x-p)); + } + else + { + // last argument + retv.push_back(text.substr(p)); + } + p = x+1; - } while (x != std::string::npos); + } while (x != std::string::npos); return retv; } diff --git a/src/debug/run_test.sh b/src/debug/run_test.sh index 953a9ddfff..854aa62061 100755 --- a/src/debug/run_test.sh +++ b/src/debug/run_test.sh @@ -14,7 +14,7 @@ echo "Building system $SERVICE..." # Get the Qemu-command (in-source, so we can use it elsewhere) . ../etc/qemu_cmd.sh export SERIAL="" #"-monitor none -virtioconsole stdio" -QEMU_OPTS+=" -drive file=./smalldisk,if=ide,media=disk $SERIAL" +QEMU_OPTS+=" -drive file=./smalldisk,if=virtio,media=disk $SERIAL" # Qemu with gdb debugging: if [ "$DEBUG" -ne 0 ] diff --git a/src/debug/service.gdb b/src/debug/service.gdb index 655fa909ae..fd52480ae6 100644 --- a/src/debug/service.gdb +++ b/src/debug/service.gdb @@ -1,4 +1,4 @@ file test_service -break VirtioNet::irq_handler +break Service::start set non-stop off target remote localhost:1234 diff --git a/src/debug/test_disk.cpp b/src/debug/test_disk.cpp index b579f126b1..11b527545d 100644 --- a/src/debug/test_disk.cpp +++ b/src/debug/test_disk.cpp @@ -2,148 +2,120 @@ #include const char* service_name__ = "..."; -//#include -//auto disk = fs::new_shared_memdisk(); - -#include -#include -using FatDisk = fs::Disk; -std::shared_ptr disk; +#include +fs::Disk_ptr disk; void list_partitions(decltype(disk)); void Service::start() { // instantiate memdisk with FAT filesystem - auto& device = hw::Dev::disk<0, hw::IDE>(hw::IDE::SLAVE); - disk = std::make_shared (device); + auto& device = hw::Dev::disk<1, VirtioBlk>(); + disk = std::make_shared (device); assert(disk); // if the disk is empty, we can't mount a filesystem anyways if (disk->empty()) panic("Oops! The disk is empty!\n"); + // 1. create alot of separate jobs + /*for (int i = 0; i < 256; i++) + device.read(0, + [i] (fs::buffer_t buffer) + { + printf("buffer %d is not null: %d\n", i, !!buffer); + assert(buffer); + }); + // 2. create alot of sequential jobs of 1024 sectors each + // note: if we queue more than this we will run out of RAM + static int bufcounter = 0; + + for (int i = 0; i < 256; i++) + device.read(0, 22, + [i] (fs::buffer_t buffer) + { + assert(buffer); + bufcounter++; + + if (bufcounter == 256) + printf("Success: All big buffers accounted for\n"); + }); + //return; + // list extended partitions list_partitions(disk); - + // mount first valid partition (auto-detect and mount) - disk->mount( // or specify partition explicitly in parameter - [] (fs::error_t err) - { - if (err) - { - printf("Could not mount filesystem\n"); + disk->mount( + [] (fs::error_t err) { + if (err) { + printf("Error: %s\n", err.to_string().c_str()); return; } - // get a reference to the mounted filesystem - auto& fs = disk->fs(); - - // check contents of disk - auto dirents = fs::new_shared_vector(); - err = fs.ls("/", dirents); - if (err) - printf("Could not list '/' directory\n"); - else - for (auto& e : *dirents) - { - printf("%s: %s\t of size %llu bytes (CL: %llu)\n", - e.type_string().c_str(), e.name().c_str(), e.size, e.block); - } + printf("Mounted filesystem\n"); - auto ent = fs.stat("/test.txt"); - // validate the stat call - if (ent.is_valid()) - { - // read specific area of file - auto buf = fs.read(ent, 1032, 65); - std::string contents((const char*) buf.buffer.get(), buf.len); - printf("[%s contents (%llu bytes)]:\n%s\n[end]\n\n", - ent.name().c_str(), buf.len, contents.c_str()); - - } - else - { - printf("Invalid entity for /test.txt\n"); - } - return; + // read something that doesn't exist + disk->fs().stat("/fefe/fefe", + [] (fs::error_t err, const fs::Dirent&) { + printf("Error: %s\n", err.to_string().c_str()); + }); + // async ls disk->fs().ls("/", - [] (fs::error_t err, auto ents) - { - if (err) - { + [] (fs::error_t err, auto ents) { + if (err) { printf("Could not list '/' directory\n"); return; } + printf("Listed filesystem root\n"); - for (auto& e : *ents) - { + // go through directory entries + for (auto& e : *ents) { printf("%s: %s\t of size %llu bytes (CL: %llu)\n", - e.type_string().c_str(), e.name().c_str(), e.size, e.block); + e.type_string().c_str(), e.name().c_str(), e.size(), e.block); - if (e.is_file()) - { - printf("*** Attempting to read: %s\n", e.name().c_str()); - disk->fs().readFile(e, - [e] (fs::error_t err, fs::buffer_t buffer, size_t len) - { - if (err) - { + if (e.is_file()) { + printf("*** Read file %s\n", e.name().c_str()); + disk->fs().read(e, 0, e.size(), + [e] (fs::error_t err, fs::buffer_t buffer, size_t len) { + if (err) { printf("Failed to read file %s!\n", - e.name().c_str()); + e.name().c_str()); return; } std::string contents((const char*) buffer.get(), len); - printf("[%s contents]:\n%s\nEOF\n\n", - e.name().c_str(), contents.c_str()); + printf("[%s contents]:\n%s\nEOF\n\n", + e.name().c_str(), contents.c_str()); }); } } - }); - - disk->fs().stat("/test.txt", - [] (fs::error_t err, const auto& e) - { - if (err) - { - printf("Could not stat %s\n", e.name().c_str()); - return; - } - - printf("stat: /test.txt is a %s on cluster %llu\n", - e.type_string().c_str(), e.block); - }); - disk->fs().stat("/Sample Pictures/Koala.jpg", - [] (fs::error_t err, const auto& e) - { - if (err) - { - printf("Could not stat %s\n", e.name().c_str()); - return; - } - - printf("stat: %s is a %s on cluster %llu\n", - e.name().c_str(), e.type_string().c_str(), e.block); - }); - + }); // ls }); // disk->auto_detect() + */ printf("*** TEST SERVICE STARTED *** \n"); + void test_APIC(); + test_APIC(); } void list_partitions(decltype(disk) disk) { disk->partitions( - [] (fs::error_t err, auto& parts) - { - if (err) - { + [] (fs::error_t err, auto& parts) { + if (err) { printf("Failed to retrieve volumes on disk\n"); return; } for (auto& part : parts) printf("[Partition] '%s' at LBA %u\n", - part.name().c_str(), part.lba()); + part.name().c_str(), part.lba()); }); } + +#include +void test_APIC() { + + hw::APIC::init(); + +} diff --git a/src/debug/test_ipv6.cpp b/src/debug/test_ipv6.cpp index 7fe76ff46d..347f39a1a7 100644 --- a/src/debug/test_ipv6.cpp +++ b/src/debug/test_ipv6.cpp @@ -40,9 +40,9 @@ void Service::start() // basic UDP service net::Inet::ifconfig( - net::ETH0, - ip4, {{255, 255, 255, 0}}, - ip6); + net::ETH0, + ip4, {{255, 255, 255, 0}}, + ip6); net::Inet* inet = net::Inet::up(); @@ -64,37 +64,37 @@ void Service::start() // basic UDP service static const int UDP_PORT = 64; inet->udp6_listen(UDP_PORT, - [=] (std::shared_ptr& pckt) -> int - { - printf("Received UDP6 packet from %s to my listener on port %d\n", - pckt->src().str().c_str(), pckt->dst_port()); + [=] (std::shared_ptr& pckt) -> int + { + printf("Received UDP6 packet from %s to my listener on port %d\n", + pckt->src().str().c_str(), pckt->dst_port()); - std::string data((const char*) pckt->data(), pckt->data_length()); + std::string data((const char*) pckt->data(), pckt->data_length()); - printf("Contents (len=%d):\n%s\n", pckt->data_length(), data.c_str()); + printf("Contents (len=%d):\n%s\n", pckt->data_length(), data.c_str()); - // unfortunately, - // copy the ether src field of the incoming packet - net::Ethernet::addr ether_src = - ((net::Ethernet::header*) pckt->buffer())->src; + // unfortunately, + // copy the ether src field of the incoming packet + net::Ethernet::addr ether_src = + ((net::Ethernet::header*) pckt->buffer())->src; - // create a response packet with destination [ether_src] dst() - std::shared_ptr newpacket = - inet->udp6_create(ether_src, pckt->dst(), UDP_PORT); + // create a response packet with destination [ether_src] dst() + std::shared_ptr newpacket = + inet->udp6_create(ether_src, pckt->dst(), UDP_PORT); - const char* text = "This is the response packet!"; - // copy text into UDP data section - memcpy( newpacket->data(), text, strlen(text) ); - // set new length - newpacket->set_length(strlen(text)); + const char* text = "This is the response packet!"; + // copy text into UDP data section + memcpy( newpacket->data(), text, strlen(text) ); + // set new length + newpacket->set_length(strlen(text)); - // generate checksum for packet before sending - newpacket->gen_checksum(); + // generate checksum for packet before sending + newpacket->gen_checksum(); - // ship it to the ether - inet->udp6_send(newpacket); - return -1; - } - ); + // ship it to the ether + inet->udp6_send(newpacket); + return -1; + } + ); } diff --git a/src/debug/test_service.cpp b/src/debug/test_service.cpp index b9d86ed653..d6206cf0d3 100644 --- a/src/debug/test_service.cpp +++ b/src/debug/test_service.cpp @@ -6,9 +6,9 @@ // 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. @@ -17,7 +17,6 @@ #include #include -#include #include "ircd.hpp" using namespace std::chrono; @@ -31,44 +30,96 @@ void Service::start() hw::Nic& eth0 = hw::Dev::eth<0,VirtioNet>(); inet = std::make_unique >(eth0); inet->network_config( - {{ 10,0,0,42 }}, // IP - {{ 255,255,255,0 }}, // Netmask - {{ 10,0,0,1 }}, // Gateway - {{ 8,8,8,8 }} ); // DNS - + { 10,0,0,42 }, // IP + { 255,255,255,0 }, // Netmask + { 10,0,0,1 }, // Gateway + { 8,8,8,8 } ); // DNS + + /* auto& tcp = inet->tcp(); auto& server = tcp.bind(6667); // IRCd default port server.onConnect( [] (auto csock) { printf("*** Received connection from %s\n", - csock->remote().to_string().c_str()); - + csock->remote().to_string().c_str()); + /// create client /// size_t index = clients.size(); clients.emplace_back(index, csock); - + auto& client = clients[index]; - + // set up callbacks csock->onReceive( [&client] (auto conn, bool) { - char buffer[1024]; - size_t bytes = conn->read(buffer, sizeof(buffer)); - - client.read(buffer, bytes); - + char buffer[1024]; + size_t bytes = conn->read(buffer, sizeof(buffer)); + + client.read(buffer, bytes); + }); - /*.onDisconnect( + + .onDisconnect( [&client] (auto conn, std::string) { - // remove client from various lists - client.remove(); - /// inform others about disconnect - //client.bcast(TK_QUIT, "Disconnected"); - });*/ - }); + // remove client from various lists + client.remove(); + /// inform others about disconnect + //client.bcast(TK_QUIT, "Disconnected"); + }); + });*/ + + inet->dhclient()->set_silent(true); + + inet->on_config( + [] (bool timeout) + { + if (timeout) + printf("Inet::on_config: Timeout\n"); + else + printf("Inet::on_config: DHCP Server acknowledged our request!\n"); + printf("Service IP address: %s, router: %s\n", + inet->ip_addr().str().c_str(), inet->router().str().c_str()); + + using namespace net; + const UDP::port_t port = 4242; + auto& sock = inet->udp().bind(port); + + sock.on_read( + [&sock] (UDP::addr_t addr, UDP::port_t port, + const char* data, size_t len) + { + std::string strdata(data, len); + CHECK(1, "Getting UDP data from %s:%d -> %s", + addr.str().c_str(), port, strdata.c_str()); + // send the same thing right back! + sock.sendto(addr, port, data, len, + [&sock, addr, port] + { + // print this message once + printf("*** Starting spam (you should see this once)\n"); + + typedef std::function rnd_gen_t; + auto next = std::make_shared (); + + *next = + [next, &sock, addr, port] () + { + // spam this message at max speed + std::string text("Spamorino Cappucino\n"); + + sock.sendto(addr, port, text.data(), text.size(), + [next] { (*next)(); }); + }; + + // start spamming + (*next)(); + }); + }); // sock on_read + + }); // on_config printf("*** TEST SERVICE STARTED *** \n"); } diff --git a/src/debug/test_tcp.cpp b/src/debug/test_tcp.cpp index 215c18aa3f..d9f00ffeb4 100644 --- a/src/debug/test_tcp.cpp +++ b/src/debug/test_tcp.cpp @@ -33,55 +33,26 @@ void Service::start() { inet = std::make_unique(eth0); inet->network_config( {{ 10,0,0,42 }}, // IP - {{ 255,255,255,0 }}, // Netmask - {{ 10,0,0,1 }}, // Gateway - {{ 8,8,8,8 }} ); // DNS + {{ 255,255,255,0 }}, // Netmask + {{ 10,0,0,1 }}, // Gateway + {{ 8,8,8,8 }} ); // DNS auto& server = inet->tcp().bind(80); hw::PIT::instance().onTimeout(5s, [server]{ - printf("Server is running: %s \n", server.to_string().c_str()); - }); - - server.onAccept([](auto conn)->bool { - printf(" onAccept \n"); - return true; - - }).onConnect([](auto conn) { - printf(" onConnect \n"); - - }).onReceive([](auto conn, bool) { - printf(" onReceive \n"); - - }).onDisconnect([](auto conn, std::string msg) { - printf(" onDisconnect \n"); - - }).onError([](auto conn, auto err) { - printf(" Error: %s \n", err.what()); - - }).onPacketReceived([](auto conn, auto packet) { - printf(" Received. \n"); - - }).onPacketDropped([](auto packet, std::string reason) { - printf(" Dropped. \n"); - - }); - - inet->dhclient()->on_config([server](auto&) { - printf(" Trying to connect to server. \n"); - auto conn = inet->tcp().connect({50,63,202,26}); - printf(" Connection: %s \n", conn->to_string().c_str()); - conn->onConnect([](auto conn) { - printf(" onConnect \n"); - conn->write("GET / HTTP/1.0"); + printf("Server is running: %s \n", server.to_string().c_str()); + }); - }).onReceive([](auto conn, bool) { - printf(" onReceive:\n%s \n", conn->read(3000).c_str()); + server.onPacketReceived([](auto conn, auto packet) { + printf(" Received: %s\n", packet->to_string().c_str()); - }).onPacketReceived([](auto conn, auto packet) { - printf(" Received: %s \n", packet->to_string().c_str()); + }).onPacketDropped([](auto packet, std::string reason) { + printf(" Dropped: %s - Reason: %s \n", packet->to_string().c_str(), reason.c_str()); - }); - }); + }).onReceive([](auto conn, bool) { + conn->write("Hey"); + }).onConnect([](auto conn) { + printf(" Connected.\n"); + }); } diff --git a/src/fs/disk.cpp b/src/fs/disk.cpp new file mode 100644 index 0000000000..28342121e7 --- /dev/null +++ b/src/fs/disk.cpp @@ -0,0 +1,153 @@ +// This file is a part of the IncludeOS unikernel - www.includeos.org +// +// Copyright 2015 Oslo and Akershus University College of Applied Sciences +// and Alfred Bratterud +// +// 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. + +#include +#include +#include + +namespace fs { + + Disk::Disk(hw::IDiskDevice& dev) + : device {dev} {} + + void Disk::partitions(on_parts_func func) { + + /** Read Master Boot Record (sector 0) */ + device.read(0, + [this, func] (hw::IDiskDevice::buffer_t data) + { + std::vector parts; + + if (!data) { + func({ error_t::E_IO, "Unable to read MBR"}, parts); + return; + } + + // First sector is the Master Boot Record + auto* mbr =(MBR::mbr*) data.get(); + + for (int i {0}; i < 4; ++i) { + // all the partitions are offsets to potential Volume Boot Records + parts.emplace_back( + mbr->part[i].flags, //< flags + mbr->part[i].type, //< id + mbr->part[i].lba_begin, //< LBA + mbr->part[i].sectors); + } + + func(no_error, parts); + }); + } + + void Disk::mount(on_mount_func func) + { + device.read(0, + [this, func] (hw::IDiskDevice::buffer_t data) + { + if (!data) { + // TODO: error-case for unable to read MBR + func({ error_t::E_IO, "Unable to read MBR"}); + return; + } + + // auto-detect FAT on MBR: + auto* mbr = (MBR::mbr*) data.get(); + MBR::BPB* bpb = mbr->bpb(); + + if (bpb->bytes_per_sector >= 512 + && bpb->fa_tables != 0 + && (bpb->signature != 0 // check MBR signature too + || bpb->large_sectors != 0)) // but its not set for FAT32 + { + // detected FAT on MBR + filesys.reset(new FAT(device)); + // mount on MBR + internal_mount(MBR, func); + return; + } + + // go through partition list + for (int i = 0; i < 4; i++) + { + if (mbr->part[i].type != 0 // 0 is unused partition + && mbr->part[i].lba_begin != 0 // 0 is MBR anyways + && mbr->part[i].sectors != 0) // 0 means no size, so... + { + // FIXME: for now we can only assume FAT, anyways + // To be replaced with lookup table for partition identifiers, + // but we really only have FAT atm, so its just wasteful + filesys.reset(new FAT(device)); + // mount on VBRn + internal_mount((partition_t) (VBR1 + i), func); + return; + } + } + + // no partition was found (TODO: extended partitions) + func({ error_t::E_MNT, "No FAT partition auto-detected"}); + }); + } + + void Disk::mount(partition_t part, on_mount_func func) + { + filesys.reset(new FAT(device)); + internal_mount(part, func); + } + + void Disk::internal_mount(partition_t part, on_mount_func func) { + + if (part == MBR) + { + // For the MBR case, all we need to do is mount on sector 0 + fs().mount(0, device.size(), func); + } + else + { + /** + * Otherwise, we will have to read the LBA offset + * of the partition to be mounted + */ + device.read(0, + [this, part, func] (hw::IDiskDevice::buffer_t data) + { + if (!data) { + // TODO: error-case for unable to read MBR + func({ error_t::E_IO, "Unable to read MBR" }); + return; + } + + auto* mbr = (MBR::mbr*) data.get(); //< Treat data as MBR + auto pint = static_cast(part - 1); //< Treat VBR1 as index 0 etc. + + /** Get LBA from selected partition */ + auto lba_base = mbr->part[pint].lba_begin; + auto lba_size = mbr->part[pint].sectors; + + /** + * Call the filesystems mount function + * with lba_begin as base address + */ + fs().mount(lba_base, lba_size, func); + }); + } + } + + std::string Disk::Partition::name() const { + return MBR::id_to_name(id); + } + +} //< namespace fs diff --git a/src/fs/ext4.cpp b/src/fs/ext4.cpp index 3fabf278e3..81959edeb7 100644 --- a/src/fs/ext4.cpp +++ b/src/fs/ext4.cpp @@ -19,24 +19,24 @@ namespace fs void EXT4::mount(uint64_t start, uint64_t size, on_mount_func on_mount) { printf("Superblock: %u bytes, Block group desc: %u bytes\n", - sizeof(superblock), sizeof(group_desc)); + sizeof(superblock), sizeof(group_desc)); assert(sizeof(superblock) == 1024); assert(sizeof(group_desc) == 64); printf("Inode table: %u\n", - sizeof(inode_table)); + sizeof(inode_table)); // read Master Boot Record (sector 0) device.read(start, - [this, start, size, on_mount] (buffer_t data) - { + [this, start, size, on_mount] (buffer_t data) { + auto* mbr = (MBR::mbr*) data.get(); assert(mbr != nullptr); - + /// now what? printf("Mounting EXT4 from LBA %llu to %llu\n", - start, size); - + start, size); + init(data.get()); }); @@ -47,20 +47,20 @@ namespace fs (void) path; } - void EXT4::readFile(const Dirent&, on_read_func callback) + Buffer EXT4::readFile(const std::string&) { - callback(true, buffer_t(), 0); + return Buffer({ error_t::E_IO, "Not implemented" }, buffer_t(), 0); } void EXT4::readFile(const std::string& strpath, on_read_func callback) { (void) strpath; - callback(true, buffer_t(), 0); + callback({ error_t::E_IO, "Not implemented" }, buffer_t(), 0); } void EXT4::stat(const std::string& strpath, on_stat_func callback) { (void) strpath; - callback(true, Dirent()); + callback({ error_t::E_NOENT, "Not implemented" }, Dirent()); } // filesystem traversal function diff --git a/src/fs/fat.cpp b/src/fs/fat.cpp index 0e5d16d1b3..1b1719f96c 100644 --- a/src/fs/fat.cpp +++ b/src/fs/fat.cpp @@ -1,24 +1,24 @@ -#define DEBUG +//#define DEBUG #include -#include #include -#include +#include #include #include -#include #include #include // for panic() #include +//#undef debug +//#define debug(...) printf(__VA_ARGS__) #define likely(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0) inline std::string trim_right_copy( - const std::string& s, - const std::string& delimiters = " \f\n\r\t\v" ) + const std::string& s, + const std::string& delimiters = " \f\n\r\t\v" ) { return s.substr( 0, s.find_last_not_of( delimiters ) + 1 ); } @@ -26,22 +26,23 @@ inline std::string trim_right_copy( namespace fs { FAT::FAT(hw::IDiskDevice& dev) - : device(dev) - { - + : device(dev) { + // } - void FAT::init(const void* base_sector) - { + void FAT::init(const void* base_sector) { + // assume its the master boot record for now auto* mbr = (MBR::mbr*) base_sector; MBR::BPB* bpb = mbr->bpb(); this->sector_size = bpb->bytes_per_sector; - if (unlikely(this->sector_size < 512)) - { - printf("Invalid sector size (%u) for FAT32 partition\n", sector_size); - printf("Are you mounting the correct partition?\n"); + + if (unlikely(this->sector_size < 512)) { + fprintf(stderr, + "Invalid sector size (%u) for FAT32 partition\n", sector_size); + fprintf(stderr, + "Are you mounting the correct partition?\n"); panic("FAT32: Invalid sector size"); } @@ -69,44 +70,48 @@ namespace fs this->sectors = bpb->small_sectors; else this->sectors = bpb->large_sectors; + // sectors per FAT (not sure about the rule here) this->sectors_per_fat = bpb->sectors_per_fat; if (this->sectors_per_fat == 0) - this->sectors_per_fat = *(uint32_t*) &mbr->boot[25]; + this->sectors_per_fat = *(uint32_t*) &mbr->boot[25]; + // root dir sectors from root entries this->root_dir_sectors = ((bpb->root_entries * 32) + (sector_size - 1)) / sector_size; + // calculate index of first data sector this->data_index = bpb->reserved_sectors + (bpb->fa_tables * this->sectors_per_fat) + this->root_dir_sectors; debug("First data sector: %u\n", this->data_index); + // number of reserved sectors is needed constantly this->reserved = bpb->reserved_sectors; debug("Reserved sectors: %u\n", this->reserved); + // number of sectors per cluster is important for calculating entry offsets this->sectors_per_cluster = bpb->sectors_per_cluster; debug("Sectors per cluster: %u\n", this->sectors_per_cluster); + // calculate number of data sectors this->data_sectors = this->sectors - this->data_index; debug("Data sectors: %u\n", this->data_sectors); + // calculate total cluster count this->clusters = this->data_sectors / this->sectors_per_cluster; debug("Total clusters: %u\n", this->clusters); // now that we're here, we can determine the actual FAT type // using the official method: - if (this->clusters < 4085) - { + if (this->clusters < 4085) { this->fat_type = FAT::T_FAT12; this->root_cluster = 2; debug("The image is type FAT12, with %u clusters\n", this->clusters); } - else if (this->clusters < 65525) - { + else if (this->clusters < 65525) { this->fat_type = FAT::T_FAT16; this->root_cluster = 2; debug("The image is type FAT16, with %u clusters\n", this->clusters); } - else - { + else { this->fat_type = FAT::T_FAT32; this->root_cluster = *(uint32_t*) &mbr->boot[33]; this->root_cluster = 2; @@ -120,424 +125,146 @@ namespace fs debug("System ID: %.8s\n", bpb->system_id); } - void FAT::mount(uint64_t base, uint64_t size, on_mount_func on_mount) - { + void FAT::mount(uint64_t base, uint64_t size, on_mount_func on_mount) { + this->lba_base = base; this->lba_size = size; // read Partition block device.read(this->lba_base, - [this, on_mount] (buffer_t data) - { + [this, on_mount] (buffer_t data) { + auto* mbr = (MBR::mbr*) data.get(); assert(mbr != nullptr); - + // verify image signature debug("OEM name: \t%s\n", mbr->oem_name); debug("MBR signature: \t0x%x\n", mbr->magic); - assert(mbr->magic == 0xAA55); + if (unlikely(mbr->magic != 0xAA55)) { + on_mount({ error_t::E_MNT, "Missing or invalid MBR signature" }); + return; + } // initialize FAT16 or FAT32 filesystem init(mbr); - + // determine which FAT version is mounted std::string inf = "ofs: " + std::to_string(lba_base) + - "size: " + std::to_string(lba_size) + - " (" + std::to_string(this->lba_size * sector_size) + " bytes)\n"; - - switch (this->fat_type) - { + "size: " + std::to_string(lba_size) + + " (" + std::to_string(this->lba_size * sector_size) + " bytes)\n"; + + switch (this->fat_type) { case FAT::T_FAT12: - INFO("FS", "Mounting FAT12 filesystem"); - break; + INFO("FS", "Mounting FAT12 filesystem"); + break; case FAT::T_FAT16: - INFO("FS", "Mounting FAT16 filesystem"); - break; + INFO("FS", "Mounting FAT16 filesystem"); + break; case FAT::T_FAT32: - INFO("FS", "Mounting FAT32 filesystem"); - break; + INFO("FS", "Mounting FAT32 filesystem"); + break; } INFO2("[ofs=%u size=%u (%u bytes)]\n", - this->lba_base, this->lba_size, this->lba_size * 512); - + this->lba_base, this->lba_size, this->lba_size * 512); + // on_mount callback on_mount(no_error); }); } - bool FAT::int_dirent( - uint32_t sector, - const void* data, - dirvec_t dirents) - { - auto* root = (cl_dir*) data; - bool found_last = false; + bool FAT::int_dirent(uint32_t sector, const void* data, dirvector& dirents) { + + auto* root = (cl_dir*) data; + bool found_last = false; - for (int i = 0; i < 16; i++) - { - if (unlikely(root[i].shortname[0] == 0x0)) - { - //printf("end of dir\n"); - found_last = true; - // end of directory - break; - } - else if (unlikely(root[i].shortname[0] == 0xE5)) - { - // unused index - } - else - { - // traverse long names, then final cluster - // to read all the relevant info - - if (likely(root[i].is_longname())) - { - auto* L = (cl_long*) &root[i]; - // the last long index is part of a chain of entries - if (L->is_last()) - { - // buffer for long filename - char final_name[256]; - int final_count = 0; - - int total = L->long_index(); - // go to the last entry and work backwards - i += total-1; - L += total-1; - - for (int idx = total; idx > 0; idx--) - { - uint16_t longname[13]; - memcpy(longname+ 0, L->first, 10); - memcpy(longname+ 5, L->second, 12); - memcpy(longname+11, L->third, 4); - - for (int j = 0; j < 13; j++) - { - // 0xFFFF indicates end of name - if (unlikely(longname[j] == 0xFFFF)) break; - // sometimes, invalid stuff are snuck into filenames - if (unlikely(longname[j] == 0x0)) break; - - final_name[final_count] = longname[j] & 0xFF; - final_count++; - } - L--; - - if (unlikely(final_count > 240)) - { - debug("Suspicious long name length, breaking...\n"); - break; - } - } - - final_name[final_count] = 0; - debug("Long name: %s\n", final_name); - - i++; // skip over the long version - // to the short version for the stats and cluster - auto* D = &root[i]; - std::string dirname(final_name, final_count); - dirname = trim_right_copy(dirname); - - dirents->emplace_back( - D->type(), - dirname, - D->dir_cluster(root_cluster), - sector, // parent block - D->size(), - D->attrib); + for (int i = 0; i < 16; i++) { + + if (unlikely(root[i].shortname[0] == 0x0)) { + //printf("end of dir\n"); + found_last = true; + // end of directory + break; + } + else if (unlikely(root[i].shortname[0] == 0xE5)) { + // unused index + } + else { + // traverse long names, then final cluster + // to read all the relevant info + + if (likely(root[i].is_longname())) { + auto* L = (cl_long*) &root[i]; + // the last long index is part of a chain of entries + if (L->is_last()) { + // buffer for long filename + char final_name[256]; + int final_count = 0; + + int total = L->long_index(); + // go to the last entry and work backwards + i += total-1; + L += total-1; + + for (int idx = total; idx > 0; idx--) { + uint16_t longname[13]; + memcpy(longname+ 0, L->first, 10); + memcpy(longname+ 5, L->second, 12); + memcpy(longname+11, L->third, 4); + + for (int j = 0; j < 13; j++) { + // 0xFFFF indicates end of name + if (unlikely(longname[j] == 0xFFFF)) break; + // sometimes, invalid stuff are snuck into filenames + if (unlikely(longname[j] == 0x0)) break; + + final_name[final_count] = longname[j] & 0xFF; + final_count++; + } + L--; + + if (unlikely(final_count > 240)) { + debug("Suspicious long name length, breaking...\n"); + break; } } - else - { - auto* D = &root[i]; - debug("Short name: %.11s\n", D->shortname); - - std::string dirname((char*) D->shortname, 11); - dirname = trim_right_copy(dirname); - - dirents->emplace_back( + + final_name[final_count] = 0; + debug("Long name: %s\n", final_name); + + i++; // skip over the long version + // to the short version for the stats and cluster + auto* D = &root[i]; + std::string dirname(final_name, final_count); + dirname = trim_right_copy(dirname); + + dirents.emplace_back( D->type(), dirname, D->dir_cluster(root_cluster), sector, // parent block D->size(), D->attrib); - } - } - } // directory list - - return found_last; - } - - void FAT::int_ls( - uint32_t sector, - dirvec_t dirents, - on_internal_ls_func callback) - { - // list contents of meme sector by sector - typedef std::function next_func_t; - - auto next = std::make_shared (); - *next = - [this, sector, callback, dirents, next] (uint32_t sector) - { - debug("int_ls: sec=%u\n", sector); - device.read(sector, - [this, sector, callback, dirents, next] (buffer_t data) - { - if (!data) - { - // could not read sector - callback(true, dirents); - return; - } - - // parse entries in sector - bool done = int_dirent(sector, data.get(), dirents); - if (done) - { - // execute callback - callback(no_error, dirents); - } - else - { - // go to next sector - (*next)(sector+1); + } } + else { + auto* D = &root[i]; + debug("Short name: %.11s\n", D->shortname); - }); // read root dir - }; - - // start reading sectors asynchronously - (*next)(sector); - } - - void FAT::traverse(std::shared_ptr path, cluster_func callback) - { - // parse this path into a stack of memes - typedef std::function next_func_t; - - // asynch stack traversal - auto next = std::make_shared (); - *next = - [this, path, next, callback] (uint32_t cluster) - { - if (path->empty()) - { - // attempt to read directory - uint32_t S = this->cl_to_sector(cluster); - - // result allocated on heap - auto dirents = std::make_shared> (); + std::string dirname((char*) D->shortname, 11); + dirname = trim_right_copy(dirname); - int_ls(S, dirents, - [callback] (error_t error, dirvec_t ents) - { - callback(error, ents); - }); - return; - } - - // retrieve next name - std::string name = path->front(); - path->pop_front(); - - uint32_t S = this->cl_to_sector(cluster); - debug("Current target: %s on cluster %u (sector %u)\n", name.c_str(), cluster, S); - - // result allocated on heap - auto dirents = std::make_shared> (); - - // list directory contents - int_ls(S, dirents, - [name, dirents, next, callback] (error_t error, dirvec_t ents) - { - if (unlikely(error)) - { - debug("Could not find: %s\n", name.c_str()); - callback(true, dirents); - return; + dirents.emplace_back( + D->type(), + dirname, + D->dir_cluster(root_cluster), + sector, // parent block + D->size(), + D->attrib); } - - // look for name in directory - for (auto& e : *ents) - { - if (unlikely(e.name() == name)) - { - // go to this directory, unless its the last name - debug("Found match for %s", name.c_str()); - // enter the matching directory - debug("\t\t cluster: %llu\n", e.block); - // only follow directories - if (e.type() == DIR) - (*next)(e.block); - else - callback(true, dirents); - return; - } - } // for (ents) - - debug("NO MATCH for %s\n", name.c_str()); - callback(true, dirents); - }); + } // entry is long name - }; - // start by reading root directory - (*next)(0); + } // directory list + return found_last; } - void FAT::ls(const std::string& path, on_ls_func on_ls) - { - // parse this path into a stack of names - auto pstk = std::make_shared (path); - - traverse(pstk, - [on_ls] (error_t error, dirvec_t dirents) - { - on_ls(error, dirents); - }); - } - - void FAT::read(const Dirent& ent, uint64_t pos, uint64_t n, on_read_func callback) - { - auto buf = read(ent, pos, n); - callback(buf.err, buf.buffer, buf.len); - } - - void FAT::readFile(const Dirent& ent, on_read_func callback) - { - // cluster -> sector - uint32_t sector = this->cl_to_sector(ent.block); - // number of sectors to read ahead - size_t chunks = ent.size / sector_size + 1; - // allocate buffer - auto* buffer = new uint8_t[chunks * sector_size]; - // at which sector we will stop - size_t total = chunks; - size_t current = 0; - - typedef std::function next_func_t; - auto next = std::make_shared (); - - *next = - [this, buffer, ent, callback, next] (uint32_t sector, size_t current, size_t total) - { - if (unlikely(current == total)) - { - // report back to HQ - debug("DONE SIZE: %lu (current=%lu, total=%lu)\n", - ent.size, current, total); - // create shared buffer - auto buffer_ptr = buffer_t(buffer, std::default_delete()); - // notify caller - callback(no_error, buffer_ptr, ent.size); - return; - } - device.read(sector, - [this, current, total, buffer, ent, &callback, sector, next] (buffer_t data) - { - if (!data) - { - // general I/O error occurred - debug("Failed to read sector %u for read()", sector); - // cleanup - delete[] buffer; - callback(true, buffer_t(), 0); - return; - } - - // copy over data - memcpy(buffer + current * sector_size, data.get(), sector_size); - // continue reading next sector - (*next)(sector+1, current+1, total); - }); - }; - - // start! - (*next)(sector, current, total); - } - - void FAT::readFile(const std::string& strpath, on_read_func callback) - { - auto path = std::make_shared (strpath); - if (unlikely(path->empty())) - { - // there is no possible file to read where path is empty - callback(true, nullptr, 0); - return; - } - debug("readFile: %s\n", path->back().c_str()); - - std::string filename = path->back(); - path->pop_back(); - - traverse(path, - [this, filename, &callback] (error_t error, dirvec_t dirents) - { - if (unlikely(error)) - { - // no path, no file! - callback(error, nullptr, 0); - return; - } - - // find the matching filename in directory - for (auto& e : *dirents) - { - if (unlikely(e.name() == filename)) - { - // read this file - readFile(e, callback); - return; - } - } - }); - } // readFile() - - void FAT::stat(const std::string& strpath, on_stat_func func) - { - auto path = std::make_shared (strpath); - if (unlikely(path->empty())) - { - // root doesn't have any stat anyways - // Note: could use ATTR_VOLUME_ID in FAT - func(true, Dirent(INVALID_ENTITY, strpath)); - return; - } - - debug("stat: %s\n", path->back().c_str()); - // extract file we are looking for - std::string filename = path->back(); - path->pop_back(); - // we need to remember this later - auto callback = std::make_shared (func); - - traverse(path, - [this, filename, callback] (error_t error, dirvec_t dirents) - { - if (unlikely(error)) - { - // no path, no file! - (*callback)(error, Dirent(INVALID_ENTITY, filename)); - return; - } - - // find the matching filename in directory - for (auto& e : *dirents) - { - if (unlikely(e.name() == filename)) - { - // read this file - (*callback)(no_error, e); - return; - } - } - - // not found - (*callback)(true, Dirent(INVALID_ENTITY, filename)); - }); - } } diff --git a/src/fs/fat_async.cpp b/src/fs/fat_async.cpp new file mode 100644 index 0000000000..245c1e87c8 --- /dev/null +++ b/src/fs/fat_async.cpp @@ -0,0 +1,268 @@ +//#define DEBUG +#include + +#include +#include +#include + +#include +#include + +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) + +inline size_t roundup(size_t n, size_t multiple) +{ + return ((n + multiple - 1) / multiple) * multiple; +} + +namespace fs +{ + void FAT::int_ls( + uint32_t sector, + dirvec_t dirents, + on_internal_ls_func callback) + { + // list contents of meme sector by sector + typedef std::function next_func_t; + + auto next = std::make_shared (); + *next = + [this, sector, callback, dirents, next] (uint32_t sector) + { + debug("int_ls: sec=%u\n", sector); + device.read(sector, + [this, sector, callback, dirents, next] (buffer_t data) { + + if (!data) { + // could not read sector + callback({ error_t::E_IO, "Unable to read directory" }, dirents); + return; + } + + // parse entries in sector + bool done = int_dirent(sector, data.get(), *dirents); + if (done) + // execute callback + callback(no_error, dirents); + else + // go to next sector + (*next)(sector+1); + + }); // read root dir + }; + + // start reading sectors asynchronously + (*next)(sector); + } + + void FAT::traverse(std::shared_ptr path, cluster_func callback) + { + // parse this path into a stack of memes + typedef std::function next_func_t; + + // asynch stack traversal + auto next = std::make_shared (); + *next = + [this, path, next, callback] (uint32_t cluster) { + + if (path->empty()) { + // attempt to read directory + uint32_t S = this->cl_to_sector(cluster); + + // result allocated on heap + auto dirents = std::make_shared> (); + + int_ls(S, dirents, + [callback] (error_t error, dirvec_t ents) { + callback(error, ents); + }); + return; + } + + // retrieve next name + std::string name = path->front(); + path->pop_front(); + + uint32_t S = this->cl_to_sector(cluster); + debug("Current target: %s on cluster %u (sector %u)\n", name.c_str(), cluster, S); + + // result allocated on heap + auto dirents = std::make_shared> (); + + // list directory contents + int_ls(S, dirents, + [name, dirents, next, callback] (error_t err, dirvec_t ents) { + + if (unlikely(err)) { + debug("Could not find: %s\n", name.c_str()); + callback(err, dirents); + return; + } + + // look for name in directory + for (auto& e : *ents) { + if (unlikely(e.name() == name)) { + // go to this directory, unless its the last name + debug("Found match for %s", name.c_str()); + // enter the matching directory + debug("\t\t cluster: %llu\n", e.block); + // only follow directories + if (e.type() == DIR) + (*next)(e.block); + else + callback({ error_t::E_NOTDIR, e.name() }, dirents); + return; + } + } // for (ents) + + debug("NO MATCH for %s\n", name.c_str()); + callback({ error_t::E_NOENT, name }, dirents); + }); + + }; + // start by reading root directory + (*next)(0); + } + + void FAT::ls(const std::string& path, on_ls_func on_ls) { + + // parse this path into a stack of names + auto pstk = std::make_shared (path); + + traverse(pstk, + [on_ls] (error_t error, dirvec_t dirents) { + on_ls(error, dirents); + }); + } + void FAT::ls(const Dirent& ent, on_ls_func on_ls) + { + auto dirents = std::make_shared (); + // verify ent is a directory + if (!ent.is_valid() || !ent.is_dir()) { + on_ls( { error_t::E_NOTDIR, ent.name() }, dirents ); + return; + } + // convert cluster to sector + uint32_t S = this->cl_to_sector(ent.block); + // read result directory entries into ents + int_ls(S, dirents, + [on_ls] (error_t err, dirvec_t entries) { + on_ls( err, entries ); + }); + } + + void FAT::read(const Dirent& ent, uint64_t pos, uint64_t n, on_read_func callback) + { + // when n=0 roundup() will return an invalid value + if (n == 0) { + callback({ error_t::E_IO, "Zero read length" }, buffer_t(), 0); + return; + } + // bounds check the read position and length + uint32_t stapos = std::min(ent.size(), pos); + uint32_t endpos = std::min(ent.size(), pos + n); + // new length + n = endpos - stapos; + // calculate start and length in sectors + uint32_t sector = stapos / this->sector_size; + uint32_t nsect = roundup(endpos, sector_size) / sector_size - sector; + uint32_t internal_ofs = stapos % device.block_size(); + + // cluster -> sector + position + device.read(this->cl_to_sector(ent.block) + sector, nsect, + [pos, n, callback, internal_ofs] (buffer_t data) { + + if (!data) { + // general I/O error occurred + debug("Failed to read sector %u for read()", sector); + callback({ error_t::E_IO, "Unable to read file" }, buffer_t(), 0); + return; + } + + // when the offset is non-zero we aren't on a sector boundary + if (internal_ofs != 0) { + // so, we need to copy offset data to data buffer + auto* result = new uint8_t[n]; + memcpy(result, data.get() + internal_ofs, n); + data = buffer_t(result, std::default_delete()); + } + + callback(no_error, data, n); + }); + } + + void FAT::readFile(const std::string& strpath, on_read_func callback) + { + auto path = std::make_shared (strpath); + if (unlikely(path->empty())) { + // there is no possible file to read where path is empty + callback({ error_t::E_NOENT, "Path is empty" }, nullptr, 0); + return; + } + debug("readFile: %s\n", path->back().c_str()); + + std::string filename = path->back(); + path->pop_back(); + + traverse(path, + [this, filename, &callback] (error_t error, dirvec_t dirents) { + + if (unlikely(error)) { + // no path, no file! + callback(error, buffer_t(), 0); + return; + } + + // find the matching filename in directory + for (auto& ent : *dirents) { + if (unlikely(ent.name() == filename)) { + // read this file + read(ent, 0, ent.size(), callback); + return; + } + } + + // file not found + callback({ error_t::E_NOENT, filename }, buffer_t(), 0); + }); + } // readFile() + + void FAT::stat(const std::string& strpath, on_stat_func func) + { + auto path = std::make_shared (strpath); + if (unlikely(path->empty())) { + // root doesn't have any stat anyways + // Note: could use ATTR_VOLUME_ID in FAT + func({ error_t::E_NOENT, "Cannot stat root" }, Dirent(INVALID_ENTITY, strpath)); + return; + } + + debug("stat: %s\n", path->back().c_str()); + // extract file we are looking for + std::string filename = path->back(); + path->pop_back(); + + traverse(path, + [this, filename, func] (error_t error, dirvec_t dirents) + { + if (unlikely(error)) { + // no path, no file! + func(error, Dirent(INVALID_ENTITY, filename)); + return; + } + + // find the matching filename in directory + for (auto& e : *dirents) { + if (unlikely(e.name() == filename)) { + // read this file + func(no_error, e); + return; + } + } + + // not found + func({ error_t::E_NOENT, filename }, Dirent(INVALID_ENTITY, filename)); + }); + } +} diff --git a/src/fs/fat_sync.cpp b/src/fs/fat_sync.cpp index 25c7286ae5..914b150714 100644 --- a/src/fs/fat_sync.cpp +++ b/src/fs/fat_sync.cpp @@ -1,145 +1,135 @@ -#define DEBUG +//#define DEBUG #include #include -#include #include #include #include #include -#include -#include // for panic() - -#include #define likely(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0) -inline std::string trim_right_copy( - const std::string& s, - const std::string& delimiters = " \f\n\r\t\v" ) +inline size_t roundup(size_t n, size_t multiple) { - return s.substr( 0, s.find_last_not_of( delimiters ) + 1 ); + return ((n + multiple - 1) / multiple) * multiple; } namespace fs { - typedef FileSystem::Buffer Buffer; - Buffer FAT::read(const Dirent& ent, uint64_t pos, uint64_t n) { - // cluster -> sector + position -> sector - uint32_t sector = this->cl_to_sector(ent.block) + pos / this->sector_size; + // bounds check the read position and length + uint32_t stapos = std::min(ent.size(), pos); + uint32_t endpos = std::min(ent.size(), pos + n); + // new length + n = endpos - stapos; + // cluster -> sector + position + uint32_t sector = stapos / this->sector_size; + uint32_t nsect = roundup(endpos, sector_size) / sector_size - sector; // the resulting buffer uint8_t* result = new uint8_t[n]; - // in all cases, read the first sector - buffer_t data = device.read_sync(sector); - - uint32_t internal_ofs = pos % device.block_size(); - // keep track of total bytes - uint64_t total = n; - // calculate bytes to read before moving on to next sector - uint32_t rest = device.block_size() - (pos - internal_ofs); - - // if what we want to read is larger than the rest, exit early - if (rest > n) - { + // read @nsect sectors ahead + buffer_t data = device.read_sync(this->cl_to_sector(ent.block) + sector, nsect); + // where to start copying from the device result + uint32_t internal_ofs = stapos % device.block_size(); + // when the offset is non-zero we aren't on a sector boundary + if (internal_ofs != 0) { + // so, we need to copy offset data to data buffer memcpy(result, data.get() + internal_ofs, n); - - return Buffer(no_error, buffer_t(result), n); + data = buffer_t(result, std::default_delete()); } - // otherwise, read to the sector border - uint8_t* ptr = result; - memcpy(ptr, data.get() + internal_ofs, rest); - ptr += rest; - n -= rest; - sector += 1; - // copy entire sectors - while (n > device.block_size()) - { - data = device.read_sync(sector); - - memcpy(ptr, data.get(), device.block_size()); - ptr += device.block_size(); - n -= device.block_size(); - sector += 1; + return Buffer(no_error, data, n); + } + + Buffer FAT::readFile(const std::string& strpath) + { + Path path(strpath); + if (unlikely(path.empty())) { + // there is no possible file to read where path is empty + return Buffer({ error_t::E_NOENT, "Path is empty" }, nullptr, 0); } + debug("readFile: %s\n", path.back().c_str()); - // copy remainder - if (likely(n > 0)) - { - data = device.read_sync(sector); - memcpy(ptr, data.get(), n); - } + std::string filename = path.back(); + path.pop_back(); - return Buffer(no_error, buffer_t(result), total); - } + // result directory entries are put into @dirents + dirvector dirents; + + auto err = traverse(path, dirents); + if (unlikely(err)) + return Buffer(err, buffer_t(), 0); // for now + + // find the matching filename in directory + for (auto& e : dirents) + if (unlikely(e.name() == filename)) { + // read this file + return read(e, 0, e.size()); + } + // entry not found + return Buffer({ error_t::E_NOENT, filename }, buffer_t(), 0); + } // readFile() - error_t FAT::int_ls(uint32_t sector, dirvec_t ents) + error_t FAT::int_ls(uint32_t sector, dirvector& ents) { bool done = false; - while (!done) - { + do { // read sector sync buffer_t data = device.read_sync(sector); - if (!data) return true; + if (unlikely(!data)) + return { error_t::E_IO, "Unable to read directory" }; // parse directory into @ents done = int_dirent(sector, data.get(), ents); // go to next sector until done sector++; - } + } while (!done); return no_error; } - error_t FAT::traverse(Path path, dirvec_t ents) + error_t FAT::traverse(Path path, dirvector& ents) { // start with root dir uint32_t cluster = 0; - // directory entries are read into this - auto dirents = new_shared_vector(); Dirent found(INVALID_ENTITY); - while (!path.empty()) - { + while (!path.empty()) { + uint32_t S = this->cl_to_sector(cluster); - dirents->clear(); // mui importante + ents.clear(); // mui importante // sync read entire directory - auto err = int_ls(S, dirents); - if (err) return err; + auto err = int_ls(S, ents); + if (unlikely(err)) return err; // the name we are looking for std::string name = path.front(); path.pop_front(); - + // check for matches in dirents - for (auto& e : *dirents) - if (unlikely(e.name() == name)) - { + for (auto& e : ents) + if (unlikely(e.name() == name)) { // go to this directory, unless its the last name debug("traverse_sync: Found match for %s", name.c_str()); // enter the matching directory debug("\t\t cluster: %lu\n", e.block); // only follow if the name is a directory - if (e.type() == DIR) - { + if (e.type() == DIR) { found = e; break; } - else - { + else { // not dir = error, for now - return true; + return { error_t::E_NOTDIR, "Cannot list non-directory" }; } } // for (ents) - + // validate result - if (found.type() == INVALID_ENTITY) - { + if (found.type() == INVALID_ENTITY) { debug("traverse_sync: NO MATCH for %s\n", name.c_str()); - return true; + return { error_t::E_NOENT, name }; } // set next cluster cluster = found.block; @@ -150,16 +140,29 @@ namespace fs return int_ls(S, ents); } - error_t FAT::ls(const std::string& strpath, dirvec_t ents) + FAT::List FAT::ls(const std::string& strpath) { - return traverse(strpath, ents); + auto ents = std::make_shared (); + auto err = traverse(strpath, *ents); + return { err, ents }; + } + FAT::List FAT::ls(const Dirent& ent) + { + auto ents = std::make_shared (); + // verify ent is a directory + if (!ent.is_valid() || !ent.is_dir()) + return { { error_t::E_NOTDIR, ent.name() }, ents }; + // convert cluster to sector + uint32_t S = this->cl_to_sector(ent.block); + // read result directory entries into ents + auto err = int_ls(S, *ents); + return { err, ents }; } FAT::Dirent FAT::stat(const std::string& strpath) { Path path(strpath); - if (unlikely(path.empty())) - { + if (unlikely(path.empty())) { // root doesn't have any stat anyways (except ATTR_VOLUME_ID in FAT) return Dirent(INVALID_ENTITY); } @@ -170,19 +173,17 @@ namespace fs path.pop_back(); // result directory entries are put into @dirents - auto dirents = std::make_shared> (); + dirvector dirents; auto err = traverse(path, dirents); - if (err) return Dirent(INVALID_ENTITY); // for now + if (unlikely(err)) + return Dirent(INVALID_ENTITY); // for now // find the matching filename in directory - for (auto& e : *dirents) - { - if (unlikely(e.name() == filename)) - { - // return this directory entry - return e; - } + for (auto& e : dirents) + if (unlikely(e.name() == filename)) { + // return this directory entry + return e; } // entry not found return Dirent(INVALID_ENTITY); diff --git a/src/fs/filesystem.cpp b/src/fs/filesystem.cpp index 8d32a929c6..be2ef7a520 100644 --- a/src/fs/filesystem.cpp +++ b/src/fs/filesystem.cpp @@ -2,6 +2,22 @@ namespace fs { - error_t no_error = false; + error_t no_error { error_t::NO_ERR, "" }; + + std::string error_t::token() const noexcept { + switch (token_) { + case NO_ERR: + return "No error"; + case E_IO: + return "General I/O error"; + case E_MNT: + return "Mounting filesystem failed"; + + case E_NOENT: + return "No such entry"; + case E_NOTDIR: + return "Not a directory"; + } + } } diff --git a/src/fs/mbr.cpp b/src/fs/mbr.cpp index 38af37731d..aae019b0ac 100644 --- a/src/fs/mbr.cpp +++ b/src/fs/mbr.cpp @@ -5,74 +5,74 @@ namespace fs std::string MBR::id_to_name(uint8_t id) { switch (id) - { - case 0x00: - return "Empty"; - case 0x01: - return "DOS 12-bit FAT"; - case 0x02: - return "XENIX root"; - case 0x03: - return "XENIX /usr"; - case 0x04: - return "DOS 3.0+ 16-bit FAT"; - case 0x05: - return "DOS 3.3+ Extended Partition"; - case 0x06: - return "DOS 3.31+ 16-bit FAT (32M+)"; - case 0x07: - return "NTFS or exFAT"; - case 0x08: - return "Commodore DOS logical FAT"; - case 0x0b: - return "WIN95 OSR2 FAT32"; - case 0x0c: - return "WIN95 OSR2 FAT32, LBA-mapped"; - case 0x0d: - return "SILICON SAFE"; - case 0x0e: - return "WIN95: DOS 16-bit FAT, LBA-mapped"; - case 0x0f: - return "WIN95: Extended partition, LBA-mapped"; - case 0x11: - return "Hidden DOS 12-bit FAT"; - case 0x12: - return "Configuration utility partition (Compaq)"; - case 0x14: - return "Hidden DOS 16-bit FAT <32M"; - case 0x16: - return "Hidden DOS 16-bit FAT >= 32M"; - case 0x27: - return "Windows RE hidden partition"; - case 0x3c: - return "PartitionMagic recovery partition"; - case 0x82: - return "Linux swap"; - case 0x83: - return "Linux native partition"; - case 0x84: - return "Hibernation partition"; - case 0x85: - return "Linux extended partition"; - case 0x86: - return "FAT16 fault tolerant volume set"; - case 0x87: - return "NTFS fault tolerant volume set"; - case 0x8e: - return "Linux Logical Volume Manager partition"; - case 0x9f: - return "BSDI (BSD/OS)"; - case 0xa6: - return "OpenBSD"; - case 0xa8: - return "Apple MacOS X (BSD-like filesystem)"; - case 0xa9: - return "NetBSD"; - case 0xaf: - return "MacOS X HFS"; - default: - return "Invalid identifier: " + std::to_string(id); - } + { + case 0x00: + return "Empty"; + case 0x01: + return "DOS 12-bit FAT"; + case 0x02: + return "XENIX root"; + case 0x03: + return "XENIX /usr"; + case 0x04: + return "DOS 3.0+ 16-bit FAT"; + case 0x05: + return "DOS 3.3+ Extended Partition"; + case 0x06: + return "DOS 3.31+ 16-bit FAT (32M+)"; + case 0x07: + return "NTFS or exFAT"; + case 0x08: + return "Commodore DOS logical FAT"; + case 0x0b: + return "WIN95 OSR2 FAT32"; + case 0x0c: + return "WIN95 OSR2 FAT32, LBA-mapped"; + case 0x0d: + return "SILICON SAFE"; + case 0x0e: + return "WIN95: DOS 16-bit FAT, LBA-mapped"; + case 0x0f: + return "WIN95: Extended partition, LBA-mapped"; + case 0x11: + return "Hidden DOS 12-bit FAT"; + case 0x12: + return "Configuration utility partition (Compaq)"; + case 0x14: + return "Hidden DOS 16-bit FAT <32M"; + case 0x16: + return "Hidden DOS 16-bit FAT >= 32M"; + case 0x27: + return "Windows RE hidden partition"; + case 0x3c: + return "PartitionMagic recovery partition"; + case 0x82: + return "Linux swap"; + case 0x83: + return "Linux native partition"; + case 0x84: + return "Hibernation partition"; + case 0x85: + return "Linux extended partition"; + case 0x86: + return "FAT16 fault tolerant volume set"; + case 0x87: + return "NTFS fault tolerant volume set"; + case 0x8e: + return "Linux Logical Volume Manager partition"; + case 0x9f: + return "BSDI (BSD/OS)"; + case 0xa6: + return "OpenBSD"; + case 0xa8: + return "Apple MacOS X (BSD-like filesystem)"; + case 0xa9: + return "NetBSD"; + case 0xaf: + return "MacOS X HFS"; + default: + return "Invalid identifier: " + std::to_string(id); + } } diff --git a/src/fs/memdisk.cpp b/src/fs/memdisk.cpp index 6ae3c5e588..b620da2156 100644 --- a/src/fs/memdisk.cpp +++ b/src/fs/memdisk.cpp @@ -6,19 +6,19 @@ // 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. -#include -#include #include +#include +#include #include #define likely(x) __builtin_expect(!!(x), 1) @@ -31,55 +31,41 @@ extern "C" { namespace fs { -MemDisk::MemDisk() noexcept - : image_start { &_DISK_START_ }, - image_end { &_DISK_END_ } -{} + MemDisk::MemDisk() noexcept + : image_start_ { &_DISK_START_ }, + image_end_ { &_DISK_END_ } + {} + + MemDisk::buffer_t MemDisk::read_sync(block_t blk) { + auto sector_loc = image_start_ + blk * block_size(); + // Disallow reading memory past disk image + if (unlikely(sector_loc >= image_end_)) + return buffer_t{}; -void MemDisk::read(block_t blk, on_read_func callback) { - auto* sector_loc = ((char*) image_start) + blk * block_size(); - // Disallow reading memory past disk image - if (unlikely(sector_loc >= image_end)) - { - callback(buffer_t()); return; + auto buffer = new uint8_t[block_size()]; + memcpy(buffer, sector_loc, block_size()); + + return buffer_t{buffer, std::default_delete()}; } - auto* buffer = new uint8_t[block_size()]; - assert( memcpy(buffer, sector_loc, block_size()) == buffer ); - - callback( buffer_t(buffer, std::default_delete()) ); -} + MemDisk::buffer_t MemDisk::read_sync(block_t blk, size_t cnt) { + auto start_loc = image_start_ + blk * block_size(); + auto end_loc = start_loc + cnt * block_size(); + + // Disallow reading memory past disk image + if (unlikely(end_loc >= image_end_)) + return buffer_t{}; + + auto buffer = new uint8_t[cnt * block_size()]; + memcpy(buffer, start_loc, cnt * block_size()); -void MemDisk::read(block_t start, block_t count, on_read_func callback) { - auto* start_loc = ((char*) image_start) + start * block_size(); - auto* end_loc = start_loc + count * block_size(); - // Disallow reading memory past disk image - if (unlikely(end_loc >= image_end)) - { - callback(buffer_t()); return; + return buffer_t{buffer, std::default_delete()}; } - - auto* buffer = new uint8_t[count * block_size()]; - assert( memcpy(buffer, start_loc, count * block_size()) == buffer ); - - callback( buffer_t(buffer, std::default_delete()) ); -} -MemDisk::buffer_t MemDisk::read_sync(block_t blk) -{ - auto* loc = ((char*) image_start) + blk * block_size(); - // Disallow reading memory past disk image - if (unlikely(loc >= image_end)) - return buffer_t(); - - auto* buffer = new uint8_t[block_size()]; - assert( memcpy(buffer, loc, block_size()) == buffer ); - - return buffer_t(buffer, std::default_delete()); -} + MemDisk::block_t MemDisk::size() const noexcept { + // we are NOT going to round up to "support" unevenly sized + // disks that are not created as multiples of sectors + return (image_end_ - image_start_) / block_size(); + } -MemDisk::block_t MemDisk::size() const noexcept { - return ((char*) image_end - (char*) image_start) / SECTOR_SIZE; -} - } //< namespace fs diff --git a/src/fs/path.cpp b/src/fs/path.cpp index e124f137ed..fbf03b5156 100644 --- a/src/fs/path.cpp +++ b/src/fs/path.cpp @@ -22,102 +22,102 @@ namespace fs { - static const char PATH_SEPARATOR = '/'; - - Path::Path() - : Path("/") - { - // uses current directory - } - Path::Path(const std::string& path) - { - // parse full path - this->state = parse(path); - - } // Path::Path(std::string) - - std::string Path::to_string() const - { - // build path - //std::stringstream ss; + static const char PATH_SEPARATOR = '/'; + + Path::Path() + : Path("/") + { + // uses current directory + } + Path::Path(const std::string& path) + { + // parse full path + this->state = parse(path); + + } // Path::Path(std::string) + + std::string Path::to_string() const + { + // build path + //std::stringstream ss; std::string ss; - for (const auto& p : this->stk) - { - ss += PATH_SEPARATOR + p; - } - // append path/ to end - ss += PATH_SEPARATOR; - return ss; - } - - int Path::parse(const std::string& path) - { - if (path.empty()) - { - // do nothing? - return 0; - } - - std::string buffer(path.size(), 0); - char lastChar = 0; - int bufi = 0; - - for (size_t i = 0; i < path.size(); i++) - { - if (path[i] == PATH_SEPARATOR) - { - if (lastChar == PATH_SEPARATOR) - { // invalid path containing // (more than one forw-slash) - return -EINVAL; - } - if (bufi) - { - name_added(std::string(buffer, 0, bufi)); - bufi = 0; - } - else if (i == 0) - { - // if the first character is / separator, - // the path is relative to root, so clear stack - stk.clear(); - } - } - else - { - buffer[bufi] = path[i]; - bufi++; - } - lastChar = path[i]; - } // parse path - if (bufi) - { - name_added(std::string(buffer, 0, bufi)); - } + for (const auto& p : this->stk) + { + ss += PATH_SEPARATOR + p; + } + // append path/ to end + ss += PATH_SEPARATOR; + return ss; + } + + int Path::parse(const std::string& path) + { + if (path.empty()) + { + // do nothing? + return 0; + } + + std::string buffer(path.size(), 0); + char lastChar = 0; + int bufi = 0; + + for (size_t i = 0; i < path.size(); i++) + { + if (path[i] == PATH_SEPARATOR) + { + if (lastChar == PATH_SEPARATOR) + { // invalid path containing // (more than one forw-slash) + return -EINVAL; + } + if (bufi) + { + name_added(std::string(buffer, 0, bufi)); + bufi = 0; + } + else if (i == 0) + { + // if the first character is / separator, + // the path is relative to root, so clear stack + stk.clear(); + } + } + else + { + buffer[bufi] = path[i]; + bufi++; + } + lastChar = path[i]; + } // parse path + if (bufi) + { + name_added(std::string(buffer, 0, bufi)); + } return 0; - } - - void Path::name_added(const std::string& name) - { - //std::cout << "Path: " << toString() << " --> " << name << std::endl; - - if (name == ".") - { - // same directory - } - /*else if (name == "..") - { - // if the stack is empty we are at root - if (stk.empty()) - { - // trying to go above root is an error (?) - return; - } + } + + void Path::name_added(const std::string& name) + { + //std::cout << "Path: " << toString() << " --> " << name << std::endl; + + if (name == ".") + { + // same directory + } + /*else if (name == "..") + { + // if the stack is empty we are at root + if (stk.empty()) + { + // trying to go above root is an error (?) + return; + } stk.pop_back(); - }*/ - else - { - // otherwise treat as directory - stk.push_back(name); - } - } + }*/ + else + { + // otherwise treat as directory + stk.push_back(name); + } + } } diff --git a/src/fs/vbr.cpp b/src/fs/vbr.cpp deleted file mode 100644 index ee9eccbf52..0000000000 --- a/src/fs/vbr.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include - -namespace fs -{ - -} diff --git a/src/hw/apic.cpp b/src/hw/apic.cpp new file mode 100644 index 0000000000..4007417d6e --- /dev/null +++ b/src/hw/apic.cpp @@ -0,0 +1,111 @@ +// This file is a part of the IncludeOS unikernel - www.includeos.org +// +// Copyright 2015 Oslo and Akershus University College of Applied Sciences +// and Alfred Bratterud +// +// 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. + +#include +#include +#include + +#define LAPIC_ID 0x20 +#define LAPIC_VER 0x30 +#define LAPIC_TPR 0x80 +#define LAPIC_EOI 0x0B0 +#define LAPIC_LDR 0x0D0 +#define LAPIC_DFR 0x0E0 +#define LAPIC_SPURIOUS 0x0F0 +#define LAPIC_ESR 0x280 +#define LAPIC_ICRL 0x300 +#define LAPIC_ICRH 0x310 +#define LAPIC_LVT_TMR 0x320 +#define LAPIC_LVT_PERF 0x340 +#define LAPIC_LVT_LINT0 0x350 +#define LAPIC_LVT_LINT1 0x360 +#define LAPIC_LVT_ERR 0x370 +#define LAPIC_TMRINITCNT 0x380 +#define LAPIC_TMRCURRCNT 0x390 +#define LAPIC_TMRDIV 0x3E0 +#define LAPIC_LAST 0x38F +#define LAPIC_DISABLE 0x10000 +#define LAPIC_SW_ENABLE 0x100 +#define LAPIC_CPUFOCUS 0x200 +#define LAPIC_NMI (4<<8) +#define TMR_PERIODIC 0x20000 +#define TMR_BASEDIV (1<<20) + +extern "C" { + void apic_enable(); +} + +namespace hw { + + static const uintptr_t IA32_APIC_BASE = 0x1B; + + uint64_t RDMSR(uint32_t addr) + { + uint32_t EAX = 0, EDX = 0; + asm volatile("rdmsr": "=a" (EAX),"=d"(EDX) : "c" (addr)); + return ((uint64_t)EDX << 32) | EAX; + } + + // a single 16-byte aligned APIC register + typedef uint32_t apic_reg; + + struct apic + { + apic(uintptr_t addr) + : base_addr(addr) {} + + bool x2apic() const noexcept { + return false; + } + + apic_reg get_id() const noexcept { + return (read(LAPIC_ID) >> 24) & 0xFF; + } + + uint32_t read(apic_reg reg) const noexcept { + auto volatile* addr = (uint32_t volatile*) base_addr; + addr[0] = reg & 0xff; + return addr[4]; + } + void write(apic_reg reg, uint32_t value) { + auto volatile* addr = (uint32_t volatile*) base_addr; + addr[0] = reg & 0xff; + addr[4] = value; + } + + uintptr_t base_addr; + }; + + //static apic lapic; + + void APIC::init() { + + const uint64_t APIC_BASE_MSR = RDMSR(IA32_APIC_BASE); + + // find the LAPICs base address + const uintptr_t APIC_BASE_ADDR = APIC_BASE_MSR & 0xFFFFF000; + printf("APIC base addr: 0x%x\n", APIC_BASE_ADDR); + // acquire infos + apic lapic = apic(APIC_BASE_ADDR); + printf("LAPIC id: 0x%x\n", lapic.get_id()); + + + apic_enable(); + + } + +} diff --git a/src/hw/apic_asm.asm b/src/hw/apic_asm.asm new file mode 100644 index 0000000000..516bbc1bcf --- /dev/null +++ b/src/hw/apic_asm.asm @@ -0,0 +1,14 @@ +USE32 + +global apic_enable + +apic_enable: + push ecx + push eax + mov ecx, 1bh + rdmsr + bts eax, 11 + wrmsr + pop eax + pop ecx + ret diff --git a/src/hw/cmos.cpp b/src/hw/cmos.cpp new file mode 100644 index 0000000000..7cd15a5694 --- /dev/null +++ b/src/hw/cmos.cpp @@ -0,0 +1,43 @@ +#include + +cmos::Time cmos::Time::hw_update() { + // We're supposed to check this before every read + while (update_in_progress()); + f.second = get(r_sec); + f.minute = get(r_min); + f.hour = get(r_hrs); + f.day_of_week = get(r_dow); + f.day_of_month = get(r_day); + f.month = get(r_month); + f.year = get(r_year); + f.century = get(r_cent); + + return *this; +} + + +std::string cmos::Time::to_string(){ + std::array str; + sprintf(str.data(), "%.2i-%.2i-%iT%.2i:%.2i:%.2iZ", + (f.century + 20) * 100 + f.year, + f.month, + f.day_of_month, + f.hour, f.minute, f.second); + return std::string(str.data(), str.size()); +} + + +int cmos::Time::day_of_year() { + static std::array days_in_normal_month = + {{ 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }}; + + int res = f.day_of_month; + auto month = f.month; + while (month --> 0) + res += days_in_normal_month[ month ]; + + if (is_leap_year(year()) && f.month > 2) + res += 1; + + return res; +} diff --git a/src/hw/cpu_freq_sampling.cpp b/src/hw/cpu_freq_sampling.cpp index c7953a683b..ebda6604d5 100644 --- a/src/hw/cpu_freq_sampling.cpp +++ b/src/hw/cpu_freq_sampling.cpp @@ -25,83 +25,83 @@ namespace hw { -/** @note C-style code here, since we're dealing with interrupt handling. - The hardware expects a pure function pointer, and asm can't (easily) - call class member functions. */ + /** @note C-style code here, since we're dealing with interrupt handling. + The hardware expects a pure function pointer, and asm can't (easily) + call class member functions. */ -// This is how you provide storage for a static constexpr variable. -constexpr MHz PIT::frequency_; + // This is how you provide storage for a static constexpr variable. + constexpr MHz PIT::frequency_; -extern "C" double _CPUFreq_ = 0; -extern "C" constexpr uint16_t _cpu_sampling_freq_divider_ = KHz(PIT::frequency()).count() * 10; // Run 1 KHz Lowest: 0xffff + extern "C" double _CPUFreq_ = 0; + extern "C" constexpr uint16_t _cpu_sampling_freq_divider_ = KHz(PIT::frequency()).count() * 10; // Run 1 KHz Lowest: 0xffff -static constexpr int do_samples_ = 20; + static constexpr int do_samples_ = 20; -std::vector _cpu_timestamps; -std::vector _cpu_freq_samples; + std::vector _cpu_timestamps; + std::vector _cpu_freq_samples; -constexpr MHz test_frequency(){ - return MHz(PIT::frequency().count() / _cpu_sampling_freq_divider_); -} + constexpr MHz test_frequency(){ + return MHz(PIT::frequency().count() / _cpu_sampling_freq_divider_); + } -MHz calculate_cpu_frequency(){ + MHz calculate_cpu_frequency(){ - // We expect the cpu_sampling_irq_handler to push in samples; - while (_cpu_timestamps.size() < do_samples_) - OS::halt(); + // We expect the cpu_sampling_irq_handler to push in samples; + while (_cpu_timestamps.size() < do_samples_) + OS::halt(); - debug("_cpu_sampling_freq_divider_ : %i \n",_cpu_sampling_freq_divider_); + debug("_cpu_sampling_freq_divider_ : %i \n",_cpu_sampling_freq_divider_); - #ifdef DEBUG - for (auto t : _cpu_timestamps) debug("%lu \n",(uint32_t)t); - #endif +#ifdef DEBUG + for (auto t : _cpu_timestamps) debug("%lu \n",(uint32_t)t); +#endif - // Subtract the time it takes to measure time :-) - auto t1 = OS::cycles_since_boot(); - OS::cycles_since_boot(); - auto t3 = OS::cycles_since_boot(); - auto overhead = (t3 - t1) * 2; + // Subtract the time it takes to measure time :-) + auto t1 = OS::cycles_since_boot(); + OS::cycles_since_boot(); + auto t3 = OS::cycles_since_boot(); + auto overhead = (t3 - t1) * 2; - debug ("Overhead: %lu \n", (uint32_t)overhead); + debug ("Overhead: %lu \n", (uint32_t)overhead); - for (size_t i = 1; i < _cpu_timestamps.size(); i++){ - // Compute delta in cycles - auto cycles = _cpu_timestamps[i] - _cpu_timestamps[i-1] + overhead; - // Cycles pr. second == Hertz - auto freq = cycles / (1 / test_frequency().count()); - _cpu_freq_samples.push_back(freq); - debug("%lu - %lu = Delta: %lu Current PIT-Freq: %f Hz CPU Freq: %f MHz \n", - (uint32_t)_cpu_timestamps[i], (uint32_t)_cpu_timestamps[i-1], - (uint32_t)cycles, Hz(test_frequency()), freq); - } + for (size_t i = 1; i < _cpu_timestamps.size(); i++){ + // Compute delta in cycles + auto cycles = _cpu_timestamps[i] - _cpu_timestamps[i-1] + overhead; + // Cycles pr. second == Hertz + auto freq = cycles / (1 / test_frequency().count()); + _cpu_freq_samples.push_back(freq); + debug("%lu - %lu = Delta: %lu Current PIT-Freq: %f Hz CPU Freq: %f MHz \n", + (uint32_t)_cpu_timestamps[i], (uint32_t)_cpu_timestamps[i-1], + (uint32_t)cycles, Hz(test_frequency()), freq); + } #ifdef DEBUG - double sum = 0; - for (auto freq : _cpu_freq_samples) - sum += freq; - double mean = sum / _cpu_freq_samples.size(); + double sum = 0; + for (auto freq : _cpu_freq_samples) + sum += freq; + double mean = sum / _cpu_freq_samples.size(); #endif - std::sort(_cpu_freq_samples.begin(), _cpu_freq_samples.end()); - double median = _cpu_freq_samples[_cpu_freq_samples.size() / 2]; + std::sort(_cpu_freq_samples.begin(), _cpu_freq_samples.end()); + double median = _cpu_freq_samples[_cpu_freq_samples.size() / 2]; - debug(" MEAN: %f MEDIAN: %f \n",mean, median); - _CPUFreq_ = median; + debug(" MEAN: %f MEDIAN: %f \n",mean, median); + _CPUFreq_ = median; - return MHz(median); + return MHz(median); -} + } -void cpu_sampling_irq_handler(){ + void cpu_sampling_irq_handler(){ - auto t2 = OS::cycles_since_boot(); + auto t2 = OS::cycles_since_boot(); - if (_cpu_timestamps.size() < do_samples_) - _cpu_timestamps.push_back(t2); + if (_cpu_timestamps.size() < do_samples_) + _cpu_timestamps.push_back(t2); - IRQ_manager::eoi(0); - return; -} + IRQ_manager::eoi(0); + return; + } } //< namespace hw diff --git a/src/hw/ide.cpp b/src/hw/ide.cpp index 8f0245ea63..3bbfcc54f5 100644 --- a/src/hw/ide.cpp +++ b/src/hw/ide.cpp @@ -38,15 +38,13 @@ #define IDE_CMD_WRITE 0x30 #define IDE_CMD_IDENTIFY 0xEC -#define IDE_MASTER 0x00 -#define IDE_SLAVE 0x10 - #define IDE_DRQ (1 << 3) #define IDE_DRDY (1 << 6) #define IDE_BUSY (1 << 7) #define IDE_CTRL_IRQ 0x3F6 #define IDE_IRQN 14 +#define IDE_BLKSZ 512 #define IDE_VENDOR_ID PCI_Device::VENDOR_INTEL #define IDE_PRODUCT_ID 0x7010 @@ -55,11 +53,11 @@ namespace hw { -IDE::IDE(hw::PCI_Device& pcidev, selector_t sel) : - _pcidev {pcidev}, - _drive {(uint8_t) ((sel == MASTER) ? 0 : 1)}, - _iobase {0U}, - _nb_blk {0U} + IDE::IDE(hw::PCI_Device& pcidev, selector_t sel) : + _pcidev {pcidev}, + _drive {(uint8_t)sel}, + _iobase {0U}, + _nb_blk {0U} { INFO("IDE","VENDOR_ID : 0x%x, PRODUCT_ID : 0x%x", _pcidev.vendor_id(), _pcidev.product_id()); INFO("IDE","Attaching to PCI addr 0x%x",_pcidev.pci_addr()); @@ -87,7 +85,7 @@ IDE::IDE(hw::PCI_Device& pcidev, selector_t sel) : /** IDE device initialization */ set_irq_mode(false); - set_drive(0xA0 | (_drive << 4)); + set_drive(0xA0 | _drive); set_nbsectors(0U); set_blocknum(0U); set_command(IDE_CMD_IDENTIFY); @@ -108,157 +106,177 @@ IDE::IDE(hw::PCI_Device& pcidev, selector_t sel) : INFO("IDE", "Initialization complete"); } -/** - * FIXME: this is a simple trick as we actually can't properly access the private - * members of the class during the IRQ handling... - */ -static IDE::on_read_func _callback = nullptr; // Current callback for asynchronous read -static int _nb_irqs = 0; // Number of IRQs that we expect - -void IDE::read(block_t blk, on_read_func callback) { - if (blk >= _nb_blk) { - // avoid reading past the disk boundaries - callback(buffer_t()); - return; - } + struct ide_irq { + ide_irq(uint8_t* buff, IDE::on_read_func call) + : buffer(buff) + , callback(call) + {} + + uint8_t* buffer; // Reading buffer + IDE::on_read_func callback; // IRQ callback + }; + + static int _nb_irqs = 0; // Number of IRQs that we expect + static IDE::on_read_func _current_callback = nullptr; // Callback for the current irq + static std::list _ide_irqs; // IRQ queue + + void IDE::read(block_t blk, on_read_func callback) { + if (blk >= _nb_blk) { + // avoid reading past the disk boundaries + callback(buffer_t()); + return; + } - callback(read_sync(blk)); - return; - - set_irq_mode(true); - set_drive(0xE0 | (_drive << 4) | ((blk >> 24) & 0x0F)); - set_nbsectors(1); - set_blocknum(blk); - set_command(IDE_CMD_READ); - - _callback = callback; - _nb_irqs = 1; -} + set_irq_mode(true); + set_drive(0xE0 | _drive | ((blk >> 24) & 0x0F)); + set_nbsectors(1); + set_blocknum(blk); + set_command(IDE_CMD_READ); -void IDE::read(block_t blk, block_t count, on_read_func callback) -{ - if (blk + count >= _nb_blk) { - // avoid reading past the disk boundaries - callback(buffer_t()); - return; + _current_callback = callback; + _nb_irqs = 1; } - - set_irq_mode(true); - set_drive(0xE0 | (_drive << 4) | ((blk >> 24) & 0x0F)); - set_nbsectors(count); - set_blocknum(blk); - set_command(IDE_CMD_READ); - - _callback = callback; - _nb_irqs = count; -} -IDE::buffer_t IDE::read_sync(block_t blk) -{ - if (blk >= _nb_blk) { - // avoid reading past the disk boundaries - return buffer_t(); + void IDE::read(block_t blk, size_t count, on_read_func callback) + { + if (blk + count >= _nb_blk) { + // avoid reading past the disk boundaries + callback(buffer_t()); + return; + } + + set_irq_mode(true); + set_drive(0xE0 | _drive | ((blk >> 24) & 0x0F)); + set_nbsectors(count); + set_blocknum(blk); + set_command(IDE_CMD_READ); + + _current_callback = callback; + _nb_irqs = count; } - set_irq_mode(false); - set_drive(0xE0 | (_drive << 4) | ((blk >> 24) & 0x0F)); - set_nbsectors(1); - set_blocknum(blk); - set_command(IDE_CMD_READ); + IDE::buffer_t IDE::read_sync(block_t blk) + { + if (blk >= _nb_blk) { + // avoid reading past the disk boundaries + return buffer_t(); + } - auto* buffer = new uint8_t[block_size()]; + set_irq_mode(false); + set_drive(0xE0 | _drive | ((blk >> 24) & 0x0F)); + set_nbsectors(1); + set_blocknum(blk); + set_command(IDE_CMD_READ); - wait_status_flags(IDE_DRDY, false); + auto* buffer = new uint8_t[block_size()]; + + wait_status_flags(IDE_DRDY, false); - uint16_t* wptr = (uint16_t*) buffer; - uint16_t* wend = (uint16_t*)&buffer[block_size()]; - while (wptr < wend) - *(wptr++) = inw(IDE_DATA); + uint16_t* wptr = (uint16_t*) buffer; + uint16_t* wend = (uint16_t*)&buffer[block_size()]; + while (wptr < wend) + *(wptr++) = inw(IDE_DATA); - // return a shared_ptr wrapper for the buffer - return buffer_t(buffer, std::default_delete()); -} + // return a shared_ptr wrapper for the buffer + return buffer_t(buffer, std::default_delete()); + } + IDE::buffer_t IDE::read_sync(block_t blk, size_t cnt) { + (void) blk; + (void) cnt; + // not yet implemented + return buffer_t(); + } -void IDE::wait_status_busy() const noexcept { - uint8_t ret; - while (((ret = inb(IDE_STATUS)) & IDE_BUSY) == IDE_BUSY); -} + void IDE::wait_status_busy() noexcept { + uint8_t ret; + while (((ret = inb(IDE_STATUS)) & IDE_BUSY) == IDE_BUSY); + } -void IDE::wait_status_flags(const int flags, const bool set) const noexcept { - wait_status_busy(); + void IDE::wait_status_flags(const int flags, const bool set) noexcept { + wait_status_busy(); - auto ret = inb(IDE_STATUS); + auto ret = inb(IDE_STATUS); - for (int i {IDE_TIMEOUT}; i; --i) { - if (set) { - if ((ret & flags) == flags) - break; - } else { - if ((ret & flags) not_eq flags) - break; - } + for (int i {IDE_TIMEOUT}; i; --i) { + if (set) { + if ((ret & flags) == flags) + break; + } else { + if ((ret & flags) not_eq flags) + break; + } - ret = inb(IDE_STATUS); + ret = inb(IDE_STATUS); + } } -} - -void IDE::set_drive(const uint8_t drive) const noexcept { - wait_status_flags(IDE_DRQ, true); - outb(IDE_DRV, drive); -} -void IDE::set_nbsectors(const uint8_t cnt) const noexcept { - wait_status_flags(IDE_DRQ, true); - outb(IDE_SECCNT, cnt); -} + void IDE::set_drive(const uint8_t drive) const noexcept { + wait_status_flags(IDE_DRQ, true); + outb(IDE_DRV, drive); + } -void IDE::set_blocknum(block_t blk) const noexcept { - wait_status_flags(IDE_DRQ, true); - outb(IDE_BLKLO, blk & 0xFF); + void IDE::set_nbsectors(const uint8_t cnt) const noexcept { + wait_status_flags(IDE_DRQ, true); + outb(IDE_SECCNT, cnt); + } - wait_status_flags(IDE_DRQ, true); - outb(IDE_BLKMID, (blk & 0xFF00) >> 8); + void IDE::set_blocknum(block_t blk) const noexcept { + wait_status_flags(IDE_DRQ, true); + outb(IDE_BLKLO, blk & 0xFF); - wait_status_flags(IDE_DRQ, true); - outb(IDE_BLKHI, (blk & 0xFF0000) >> 16); -} + wait_status_flags(IDE_DRQ, true); + outb(IDE_BLKMID, (blk & 0xFF00) >> 8); -void IDE::set_command(const uint16_t command) const noexcept { - wait_status_flags(IDE_DRDY, false); - outb(IDE_CMD, command); -} + wait_status_flags(IDE_DRQ, true); + outb(IDE_BLKHI, (blk & 0xFF0000) >> 16); + } -void IDE::set_irq_mode(const bool on) const noexcept { - wait_status_flags(IDE_DRDY, false); - outb(IDE_CTRL_IRQ, on ? 0 : 1); -} + void IDE::set_command(const uint16_t command) const noexcept { + wait_status_flags(IDE_DRDY, false); + outb(IDE_CMD, command); + } -void IDE::irq_handler() { - if (!_nb_irqs || _callback == nullptr) { - set_irq_mode(false); - IRQ_manager::eoi(IDE_IRQN); - return; + void IDE::set_irq_mode(const bool on) noexcept { + wait_status_flags(IDE_DRDY, false); + outb(IDE_CTRL_IRQ, on ? 0 : 1); } - auto* buffer = new uint8_t[block_size()]; + extern "C" void ide_irq_handler() { + if (!_nb_irqs || _current_callback == nullptr) { + IDE::set_irq_mode(false); + IRQ_manager::eoi(IDE_IRQN); + return; + } - wait_status_flags(IDE_DRDY, false); + uint8_t* buffer = new uint8_t[IDE_BLKSZ]; - uint16_t* wptr = (uint16_t*) buffer; + IDE::wait_status_flags(IDE_DRDY, false); - for (block_t i = 0; i < block_size() / sizeof (uint16_t); ++i) - wptr[i] = inw(IDE_DATA); + uint16_t* wptr = (uint16_t*) buffer; - _callback(buffer_t(buffer, std::default_delete())); - _nb_irqs--; + for (IDE::block_t i = 0; i < IDE_BLKSZ / sizeof (uint16_t); ++i) + wptr[i] = inw(IDE_DATA); - IRQ_manager::eoi(IDE_IRQN); -} + _ide_irqs.push_back(ide_irq(buffer, _current_callback)); + _nb_irqs--; -void IDE::enable_irq_handler() { - auto del(delegate::from(this)); - IRQ_manager::enable_irq(IDE_IRQN); - IRQ_manager::subscribe(IDE_IRQN, del); -} + IRQ_manager::register_interrupt(IDE_IRQN); + IRQ_manager::eoi(IDE_IRQN); + } + + extern "C" void ide_irq_entry(); + + void IDE::callback_wrapper() + { + IDE::on_read_func callback = _ide_irqs.front().callback; + callback(IDE::buffer_t(_ide_irqs.front().buffer, std::default_delete())); + _ide_irqs.pop_front(); + } + + void IDE::enable_irq_handler() { + auto del(delegate::from(this)); + IRQ_manager::subscribe(IDE_IRQN, del); + IRQ_manager::set_handler(IDE_IRQN + 32, ide_irq_entry); + } } //< namespace hw diff --git a/src/hw/nic.cpp b/src/hw/nic.cpp index e6c40ccf1a..74d9df8dbd 100644 --- a/src/hw/nic.cpp +++ b/src/hw/nic.cpp @@ -19,46 +19,46 @@ #include /* -namespace hw { + namespace hw { */ /* -template <> const char* Nic::name(){ + template <> const char* Nic::name(){ //return "Fantastic VirtioNic No.1"; return driver.name(); -} + } */ /* -template <> Nic::Nic(PCI_Device* _dev) + template <> Nic::Nic(PCI_Device* _dev) : pcidev(_dev) //Device(this) -{ + { printf("\n Nic at PCI addr 0x%x scanning for resources\n",_dev->pci_addr()); _dev->probe_resources(); -} + } */ /* -template <> const char* Nic::name(){ + template <> const char* Nic::name(){ return "Specialized E1000 No.1"; -} + } -template <> Nic::Nic(PCI_Device* _dev) + template <> Nic::Nic(PCI_Device* _dev) : pcidev(_dev) //Device(this) -{ + { printf("\n Nic at PCI addr 0x%x scanning for resources\n",_dev->pci_addr()); _dev->probe_resources(); -} + } */ /* -} //< namespace hw + } //< namespace hw */ diff --git a/src/hw/pci_device.cpp b/src/hw/pci_device.cpp index 749363fcff..f4207c6c1c 100644 --- a/src/hw/pci_device.cpp +++ b/src/hw/pci_device.cpp @@ -24,129 +24,129 @@ namespace hw { -constexpr int NUM_CLASSCODES {19}; - -static const char* classcodes[NUM_CLASSCODES] { - "Too-Old-To-Tell", // 0 - "Mass Storage Controller", // 1 - "Network Controller", // 2 - "Display Controller", // 3 - "Multimedia Controller", // 4 - "Memory Controller", // 5 - "Bridge", // 6 - "Simple communications controllers", - "Base system peripherals", // 8 - "Inupt device", // 9 - "Docking Station", - "Processor", - "Serial Bus Controller", - "Wireless Controller", - "Intelligent I/O Controller", - "Satellite Communication Controller", // 15 - "Encryption/Decryption Controller", // 16 - "Data Acquisition and Signal Processing Controller", // 17 - NULL -}; - -constexpr int SS_BR {3}; - -static const char* bridge_subclasses[SS_BR] { - "Host", - "ISA", - "Other" -}; - -constexpr int SS_NIC {2}; - -static const char* nic_subclasses[SS_NIC] { - "Ethernet", - "Other" -}; - -struct _pci_vendor { - uint16_t id; - const char* name; -} _pci_vendorlist[] { - {0x8086,"Intel Corp."}, - {0x1013,"Cirrus Logic"}, - {0x10EC,"Realtek Semi.Corp."}, - {0x1AF4,"Virtio (Rusty Russell)"}, // Virtio creator - {0x1022,"AMD"}, - {0x0000,NULL} -}; - -static unsigned long pci_size(const unsigned long base, const unsigned long mask) noexcept { - // Find the significant bits - unsigned long size = mask & base; - - // Get the lowest of them to find the decode size - size &= ~(size - 1); - - return size; -} + constexpr int NUM_CLASSCODES {19}; + + static const char* classcodes[NUM_CLASSCODES] { + "Too-Old-To-Tell", // 0 + "Mass Storage Controller", // 1 + "Network Controller", // 2 + "Display Controller", // 3 + "Multimedia Controller", // 4 + "Memory Controller", // 5 + "Bridge", // 6 + "Simple communications controllers", + "Base system peripherals", // 8 + "Inupt device", // 9 + "Docking Station", + "Processor", + "Serial Bus Controller", + "Wireless Controller", + "Intelligent I/O Controller", + "Satellite Communication Controller", // 15 + "Encryption/Decryption Controller", // 16 + "Data Acquisition and Signal Processing Controller", // 17 + NULL + }; + + constexpr int SS_BR {3}; + + static const char* bridge_subclasses[SS_BR] { + "Host", + "ISA", + "Other" + }; + + constexpr int SS_NIC {2}; + + static const char* nic_subclasses[SS_NIC] { + "Ethernet", + "Other" + }; + + struct _pci_vendor { + uint16_t id; + const char* name; + } _pci_vendorlist[] { + {0x8086,"Intel Corp."}, + {0x1013,"Cirrus Logic"}, + {0x10EC,"Realtek Semi.Corp."}, + {0x1AF4,"Virtio (Rusty Russell)"}, // Virtio creator + {0x1022,"AMD"}, + {0x0000,NULL} + }; + + static unsigned long pci_size(const unsigned long base, const unsigned long mask) noexcept { + // Find the significant bits + unsigned long size = mask & base; + + // Get the lowest of them to find the decode size + size &= ~(size - 1); + + return size; + } -uint32_t PCI_Device::iobase() const noexcept { - assert(res_io_ != nullptr); - return res_io_->start_; -}; + uint32_t PCI_Device::iobase() const noexcept { + assert(res_io_ != nullptr); + return res_io_->start_; + }; -void PCI_Device::probe_resources() noexcept { - //Find resources on this PCI device (scan the BAR's) - uint32_t value {PCI::WTF}; + void PCI_Device::probe_resources() noexcept { + //Find resources on this PCI device (scan the BAR's) + uint32_t value {PCI::WTF}; - uint32_t reg {0}; - uint32_t len {0}; + uint32_t reg {0}; + uint32_t len {0}; - for(int bar {0}; bar < 6; ++bar) { - //Read the current BAR register - reg = PCI::CONFIG_BASE_ADDR_0 + (bar << 2); - value = read_dword(reg); + for(int bar {0}; bar < 6; ++bar) { + //Read the current BAR register + reg = PCI::CONFIG_BASE_ADDR_0 + (bar << 2); + value = read_dword(reg); - if (!value) continue; + if (!value) continue; - //Write all 1's to the register, to get the length value (osdev) - write_dword(reg, 0xFFFFFFFF); - len = read_dword(reg); + //Write all 1's to the register, to get the length value (osdev) + write_dword(reg, 0xFFFFFFFF); + len = read_dword(reg); - //Put the value back - write_dword(reg, value); + //Put the value back + write_dword(reg, value); - uint32_t unmasked_val {0}; - uint32_t pci__size {0}; + uint32_t unmasked_val {0}; + uint32_t pci__size {0}; - if (value & 1) { // Resource type IO + if (value & 1) { // Resource type IO - unmasked_val = value & PCI::BASE_ADDRESS_IO_MASK; - pci__size = pci_size(len, PCI::BASE_ADDRESS_IO_MASK & 0xFFFF); + unmasked_val = value & PCI::BASE_ADDRESS_IO_MASK; + pci__size = pci_size(len, PCI::BASE_ADDRESS_IO_MASK & 0xFFFF); - // Add it to resource list - add_resource(new Resource(unmasked_val, pci__size), res_io_); - assert(res_io_ != nullptr); + // Add it to resource list + add_resource(new Resource(unmasked_val, pci__size), res_io_); + assert(res_io_ != nullptr); - } else { //Resource type Mem + } else { //Resource type Mem - unmasked_val = value & PCI::BASE_ADDRESS_MEM_MASK; - pci__size = pci_size(len, PCI::BASE_ADDRESS_MEM_MASK); + unmasked_val = value & PCI::BASE_ADDRESS_MEM_MASK; + pci__size = pci_size(len, PCI::BASE_ADDRESS_MEM_MASK); - //Add it to resource list - add_resource(new Resource(unmasked_val, pci__size), res_mem_); - assert(res_mem_ != nullptr); - } + //Add it to resource list + add_resource(new Resource(unmasked_val, pci__size), res_mem_); + assert(res_mem_ != nullptr); + } + INFO2(""); + INFO2("[ Resource @ BAR %i ]", bar); + INFO2(" Address: 0x%x Size: 0x%x", unmasked_val, pci__size); + INFO2(" Type: %s", value & 1 ? "IO Resource" : "Memory Resource"); + } + INFO2(""); - INFO2("[ Resource @ BAR %i ]", bar); - INFO2(" Address: 0x%x Size: 0x%x", unmasked_val, pci__size); - INFO2(" Type: %s", value & 1 ? "IO Resource" : "Memory Resource"); } - - INFO2(""); -} -PCI_Device::PCI_Device(const uint16_t pci_addr, const uint32_t device_id) noexcept: + PCI_Device::PCI_Device(const uint16_t pci_addr, const uint32_t device_id) noexcept: pci_addr_{pci_addr}, device_id_{device_id} -// Device(Device::PCI) -// Why not inherit Device? Well, I think "PCI devices" are too general to be useful by itself, -// and the "Device" class is Public ABI, so it should only know about stuff that's relevant for the user. + // Device(Device::PCI) + // Why not inherit Device? Well, I think "PCI devices" are too general to be useful by itself, + // and the "Device" class is Public ABI, so it should only know about stuff that's relevant for the user. { //We have device, so probe for details devtype_.reg = read_dword(pci_addr, PCI::CONFIG_CLASS_REV); @@ -159,14 +159,14 @@ PCI_Device::PCI_Device(const uint16_t pci_addr, const uint32_t device_id) noexce switch (devtype_.classcode) { case PCI::BRIDGE: INFO2("+--+ %s %s (0x%x)", - bridge_subclasses[devtype_.subclass < SS_BR ? devtype_.subclass : SS_BR-1], - classcodes[devtype_.classcode],devtype_.subclass); + bridge_subclasses[devtype_.subclass < SS_BR ? devtype_.subclass : SS_BR-1], + classcodes[devtype_.classcode],devtype_.subclass); break; case PCI::NIC: INFO2("+--+ %s %s (0x%x)", - nic_subclasses[devtype_.subclass < SS_NIC ? devtype_.subclass : SS_NIC-1], - classcodes[devtype_.classcode],devtype_.subclass); + nic_subclasses[devtype_.subclass < SS_NIC ? devtype_.subclass : SS_NIC-1], + classcodes[devtype_.classcode],devtype_.subclass); break; default: @@ -179,39 +179,39 @@ PCI_Device::PCI_Device(const uint16_t pci_addr, const uint32_t device_id) noexce } -void PCI_Device::write_dword(const uint8_t reg, const uint32_t value) noexcept { - PCI::msg req; + void PCI_Device::write_dword(const uint8_t reg, const uint32_t value) noexcept { + PCI::msg req; - req.data = 0x80000000; - req.addr = pci_addr_; - req.reg = reg; + req.data = 0x80000000; + req.addr = pci_addr_; + req.reg = reg; - outpd(PCI::CONFIG_ADDR, static_cast(0x80000000) | req.data); - outpd(PCI::CONFIG_DATA, value); -} + outpd(PCI::CONFIG_ADDR, static_cast(0x80000000) | req.data); + outpd(PCI::CONFIG_DATA, value); + } -uint32_t PCI_Device::read_dword(const uint8_t reg) noexcept { - PCI::msg req; + uint32_t PCI_Device::read_dword(const uint8_t reg) noexcept { + PCI::msg req; - req.data = 0x80000000; - req.addr = pci_addr_; - req.reg = reg; + req.data = 0x80000000; + req.addr = pci_addr_; + req.reg = reg; - outpd(PCI::CONFIG_ADDR, static_cast(0x80000000) | req.data); + outpd(PCI::CONFIG_ADDR, static_cast(0x80000000) | req.data); - return inpd(PCI::CONFIG_DATA); -} + return inpd(PCI::CONFIG_DATA); + } -uint32_t PCI_Device::read_dword(const uint16_t pci_addr, const uint8_t reg) noexcept { - PCI::msg req; + uint32_t PCI_Device::read_dword(const uint16_t pci_addr, const uint8_t reg) noexcept { + PCI::msg req; - req.data = 0x80000000; - req.addr = pci_addr; - req.reg = reg; + req.data = 0x80000000; + req.addr = pci_addr; + req.reg = reg; - outpd(PCI::CONFIG_ADDR, static_cast(0x80000000) | req.data); + outpd(PCI::CONFIG_ADDR, static_cast(0x80000000) | req.data); - return inpd(PCI::CONFIG_DATA); -} + return inpd(PCI::CONFIG_DATA); + } } //< namespace hw diff --git a/src/hw/pic.cpp b/src/hw/pic.cpp index 3df746d7fa..c10adaf9e0 100644 --- a/src/hw/pic.cpp +++ b/src/hw/pic.cpp @@ -20,19 +20,20 @@ namespace hw { -uint16_t PIC::irq_mask_ {0xFFFF}; + uint16_t PIC::irq_mask_ {0xFFFF}; -void PIC::init() noexcept { - hw::outb(master_ctrl, master_icw1); - hw::outb(slave_ctrl, slave_icw1); - hw::outb(master_mask, master_icw2); - hw::outb(slave_mask, slave_icw2); - hw::outb(master_mask, master_icw3); - hw::outb(slave_mask, slave_icw3); - hw::outb(master_mask, master_icw4); - hw::outb(slave_mask, slave_icw4); + void PIC::init() noexcept { - set_intr_mask(irq_mask_); -} + hw::outb(master_ctrl, icw1 | icw1_icw4_needed); + hw::outb(slave_ctrl, icw1 | icw1_icw4_needed); + hw::outb(master_mask, icw2_irq_base_master); + hw::outb(slave_mask, icw2_irq_base_slave); + hw::outb(master_mask, icw3_slave_location); + hw::outb(slave_mask, icw3_slave_id); + hw::outb(master_mask, icw4_8086_mode | icw4_auto_eoi); + hw::outb(slave_mask, icw4_8086_mode); // AEOI-mode only works for master + + set_intr_mask(irq_mask_); + } } //< namespace hw diff --git a/src/hw/pit.cpp b/src/hw/pit.cpp index efbb8ae282..73264c5e49 100644 --- a/src/hw/pit.cpp +++ b/src/hw/pit.cpp @@ -16,7 +16,7 @@ // limitations under the License. //#define DEBUG -// #define DEBUG2 +//#define DEBUG2 #include #include #include @@ -25,263 +25,272 @@ namespace hw { -// Bit 0-3: Mode 0 - "Interrupt on terminal count" -// Bit 4-5: Both set, access mode "Lobyte / Hibyte" -const uint8_t PIT_mode_register = 0x43; -const uint8_t PIT_chan0 = 0x40; + // Bit 0-3: Mode 0 - "Interrupt on terminal count" + // Bit 4-5: Both set, access mode "Lobyte / Hibyte" + const uint8_t PIT_mode_register = 0x43; + const uint8_t PIT_chan0 = 0x40; -// PIT state -PIT::Mode PIT::current_mode_ = NONE; -PIT::Mode PIT::temp_mode_ = NONE; -uint16_t PIT::current_freq_divider_ = 0; -uint16_t PIT::temp_freq_divider_ = 0; + // PIT state + PIT::Mode PIT::current_mode_ = NONE; + PIT::Mode PIT::temp_mode_ = NONE; + uint16_t PIT::current_freq_divider_ = 0; + uint16_t PIT::temp_freq_divider_ = 0; -uint64_t PIT::IRQ_counter_ = 0; + uint64_t PIT::IRQ_counter_ = 0; -// Used for cpu frequency sampling -extern "C" double _CPUFreq_; -extern "C" uint16_t _cpu_sampling_freq_divider_; -extern "C" void irq_timer_entry(); + // Used for cpu frequency sampling + extern "C" double _CPUFreq_; + extern "C" uint16_t _cpu_sampling_freq_divider_; + extern "C" void irq_timer_entry(); -// Time keeping -uint64_t PIT::millisec_counter = 0; + // Time keeping + uint64_t PIT::millisec_counter = 0; -// The default recurring timer condition -std::function PIT::forever = []{ return true; }; + // The default recurring timer condition + std::function PIT::forever = []{ return true; }; -// Timer ID's -uint32_t PIT::Timer::timers_count_ = 0; + // Timer ID's + uint32_t PIT::Timer::timers_count_ = 0; -using namespace std::chrono; + using namespace std::chrono; -PIT::Timer::Timer(Type t, timeout_handler handler, std::chrono::milliseconds ms, repeat_condition cond) - : type_{t}, id_{++timers_count_}, handler_{handler}, interval_{ms}, cond_{cond} {}; + PIT::Timer::Timer(Type t, timeout_handler handler, std::chrono::milliseconds ms, repeat_condition cond) + : type_{t}, id_{++timers_count_}, handler_{handler}, interval_{ms}, cond_{cond} {}; -void PIT::disable_regular_interrupts() -{ - oneshot(1); -} + void PIT::disable_regular_interrupts() + { + oneshot(1); + } -PIT::~PIT(){} + PIT::~PIT(){} -PIT::PIT(){ - debug(" Instantiating. \n"); + PIT::PIT(){ + debug(" Instantiating. \n"); - auto handler(IRQ_manager::irq_delegate::from(this)); + auto handler(IRQ_manager::irq_delegate::from(this)); - IRQ_manager::subscribe(0, handler); -} + IRQ_manager::subscribe(0, handler); + } -void PIT::estimateCPUFrequency(){ + void PIT::estimateCPUFrequency(){ - debug(" Saving state: curr_freq_div %i \n",current_freq_divider_); - // Save PIT-state - temp_mode_ = current_mode_; - temp_freq_divider_ = current_freq_divider_; + debug(" Saving state: curr_freq_div %i \n",current_freq_divider_); + // Save PIT-state + temp_mode_ = current_mode_; + temp_freq_divider_ = current_freq_divider_; - auto prev_irq_handler = IRQ_manager::get_handler(32); + auto prev_irq_handler = IRQ_manager::get_handler(32); - debug(" Sampling\n"); - IRQ_manager::set_handler(32, cpu_sampling_irq_entry); + debug(" Sampling\n"); + IRQ_manager::set_handler(32, cpu_sampling_irq_entry); - // GO! - set_mode(RATE_GEN); - set_freq_divider(_cpu_sampling_freq_divider_); + // GO! + set_mode(RATE_GEN); + set_freq_divider(_cpu_sampling_freq_divider_); - // BLOCKING call to external measurment. - calculate_cpu_frequency(); + // BLOCKING call to external measurment. + calculate_cpu_frequency(); - debug(" Done. Result: %f \n", _CPUFreq_); + debug(" Done. Result: %f \n", _CPUFreq_); - set_mode(temp_mode_); - set_freq_divider(temp_freq_divider_); + set_mode(temp_mode_); + set_freq_divider(temp_freq_divider_); - IRQ_manager::set_handler(32, prev_irq_handler); -} + IRQ_manager::set_handler(32, prev_irq_handler); + } -MHz PIT::CPUFrequency(){ - if (! _CPUFreq_) - estimateCPUFrequency(); + MHz PIT::CPUFrequency(){ + if (! _CPUFreq_) + estimateCPUFrequency(); - return MHz(_CPUFreq_); -} + return MHz(_CPUFreq_); + } -void PIT::start_timer(Timer t, std::chrono::milliseconds in_msecs){ - if (in_msecs < 1ms) panic("Can't wait less than 1 ms. "); + PIT::Timer_iterator PIT::start_timer(Timer t, std::chrono::milliseconds in_msecs){ + if (in_msecs < 1ms) panic("Can't wait less than 1 ms. "); - if (current_mode_ != RATE_GEN) - set_mode(RATE_GEN); + if (current_mode_ != RATE_GEN) + set_mode(RATE_GEN); - if (current_freq_divider_ != millisec_interval) - set_freq_divider(millisec_interval); + if (current_freq_divider_ != millisec_interval) + set_freq_divider(millisec_interval); - auto cycles_pr_millisec = KHz(CPUFrequency()); - debug(" CPU KHz: %f Cycles to wait: %f \n",cycles_pr_millisec.count(), cycles_pr_millisec.count() * in_msecs); + auto cycles_pr_millisec = KHz(CPUFrequency()); + //debug(" CPU KHz: %f Cycles to wait: %f \n",cycles_pr_millisec.count(), cycles_pr_millisec.count() * in_msecs); - auto ticks = in_msecs / KHz(current_frequency()).count(); - debug(" PIT KHz: %f * %i = %f ms. \n", - KHz(current_frequency()).count(), (uint32_t)ticks.count(), ((uint32_t)ticks.count() * KHz(current_frequency()).count())); + auto ticks = in_msecs / KHz(current_frequency()).count(); + //debug(" PIT KHz: %f * %i = %f ms. \n", + //KHz(current_frequency()).count(), (uint32_t)ticks.count(), ((uint32_t)ticks.count() * KHz(current_frequency()).count())); - t.setStart(OS::cycles_since_boot()); - t.setEnd(t.start() + uint64_t(cycles_pr_millisec.count() * in_msecs.count())); + t.setStart(OS::cycles_since_boot()); + t.setEnd(t.start() + uint64_t(cycles_pr_millisec.count() * in_msecs.count())); - auto key = millisec_counter + ticks.count(); + auto key = millisec_counter + ticks.count(); - // We could emplace, but the timer exists allready, and might be a reused one - timers_.insert(std::make_pair(key, t)); + // We could emplace, but the timer exists allready, and might be a reused one + auto it = timers_.insert(std::make_pair(key, t)); - debug(" Key: %i id: %i, t.cond()(): %s There are %i timers. \n", - (uint32_t)key, t.id(), t.cond()() ? "true" : "false", timers_.size()); + debug(" Key: %i id: %i, t.cond()(): %s There are %i timers. \n", + (uint32_t)key, t.id(), t.cond()() ? "true" : "false", timers_.size()); + return it; -} + } -void PIT::onRepeatedTimeout(std::chrono::milliseconds ms, timeout_handler handler, repeat_condition cond){ - debug(" setting a %i ms. repeating timer \n", (uint32_t)ms.count()); + PIT::Timer_iterator PIT::onRepeatedTimeout(std::chrono::milliseconds ms, timeout_handler handler, repeat_condition cond){ + debug(" setting a %i ms. repeating timer \n", (uint32_t)ms.count()); - Timer t(Timer::REPEAT_WHILE, handler, ms, cond); - start_timer(t, ms); -}; + Timer t(Timer::REPEAT_WHILE, handler, ms, cond); + return start_timer(t, ms); + }; -void PIT::onTimeout(std::chrono::milliseconds msec, timeout_handler handler){ - Timer t(Timer::ONE_SHOT, handler, msec); + PIT::Timer_iterator PIT::onTimeout(std::chrono::milliseconds msec, timeout_handler handler){ + Timer t(Timer::ONE_SHOT, handler, msec); - debug(" setting a %i ms. one-shot timer. Id: %i \n", - (uint32_t)msec.count(), t.id()); + debug(" setting a %i ms. one-shot timer. Id: %i \n", + (uint32_t)msec.count(), t.id()); - start_timer(t, msec); + return start_timer(t, msec); -}; + }; + void PIT::stop_timer(PIT::Timer_iterator it) { + timers_.erase(it); + } -uint8_t PIT::read_back(uint8_t){ - const uint8_t READ_BACK_CMD = 0xc2; - hw::outb(PIT_mode_register, READ_BACK_CMD ); + uint8_t PIT::read_back(uint8_t){ + const uint8_t READ_BACK_CMD = 0xc2; - auto res = hw::inb(PIT_chan0); + hw::outb(PIT_mode_register, READ_BACK_CMD ); - debug("STATUS: %#x \n", res); + auto res = hw::inb(PIT_chan0); - return res; + debug("STATUS: %#x \n", res); -} + return res; -void PIT::irq_handler(){ + } - IRQ_counter_ ++; + void PIT::irq_handler(){ + // All IRQ-handlers has to send EOI + IRQ_manager::eoi(0); - if (current_freq_divider_ == millisec_interval) - millisec_counter++; + IRQ_counter_ ++; - #ifdef DEBUG - if (millisec_counter % 100 == 0) - OS::rsprint("."); - #endif + if (current_freq_divider_ == millisec_interval) + millisec_counter++; - // Iterate over expired timers (we break on the first non-expired) - for (auto it = timers_.begin(); it != timers_.end(); it++) { +#ifdef DEBUG + if (millisec_counter % 100 == 0) + OS::rsprint("."); +#endif - // Map-keys are sorted. If this timer isn't expired, neither are the rest - if (it->first > millisec_counter) - break; + std::vector garbage {}; + std::vector restart {}; - debug2 ("\n**** Timer type %i, id: %i expired. Running handler **** \n", - it->second.type(), it->second.id()); + // Iterate over expired timers (we break on the first non-expired) + for (auto it = timers_.begin(); it != timers_.end(); it++) { - // Execute the handler - it->second.handler()(); + // Map-keys are sorted. If this timer isn't expired, neither are the rest + if (it->first > millisec_counter) + break; - // Re-queue repeating timers - if (it->second.type() == Timer::REPEAT) { - debug2 (" REPEAT: Requeuing the timer \n"); - start_timer(it->second, it->second.interval()); + debug2 ("\n**** Timer type %i, id: %i expired. Running handler **** \n", + it->second.type(), it->second.id()); - }else if (it->second.type() == Timer::REPEAT_WHILE and it->second.cond()()) { - debug2 (" REPEAT_WHILE: Requeuing the timer COND \n"); - start_timer(it->second, it->second.interval()); - } + // Execute the handler + it->second.handler()(); - debug2 ("Timer done. Erasing. \n"); + // Re-queue repeating timers + if (it->second.type() == Timer::REPEAT) { + debug2 (" REPEAT: Requeuing the timer \n"); + restart.push_back(it); - // Escape iterator death - auto remove = it; - it++; + }else if (it->second.type() == Timer::REPEAT_WHILE and it->second.cond()()) { + debug2 (" REPEAT_WHILE: Requeuing the timer COND \n"); + restart.push_back(it); + } - // Erase the timer - timers_.erase(remove); + debug2 ("Timer done. Erasing. \n"); - // If this was the last timer, we can turn off the clock - if (timers_.empty()){ - // Disable the PIT - oneshot(1); + // Queue this timer for deletion (Escape iterator death by not deleting it now) + garbage.push_back(it); - debug2 ("Timers done. PIT disabled for now. \n"); - // Escape iterator death - break; } - debug2 ("Timers left: %i \n", timers_.size()); + if (not garbage.empty()) { + debug2 ("Timers left: %i \n", timers_.size()); - #ifdef DEBUG2 - for (auto t : timers_) - debug2("Key: %i , id: %i, Type: %i", (uint32_t)t.first, t.second.id(), t.second.type()); - #endif +#ifdef DEBUG2 + for (auto t : timers_) + debug2("Key: %i , id: %i, Type: %i \n", (uint32_t)t.first, t.second.id(), t.second.type()); +#endif - debug2("\n---------------------------\n\n"); - } + debug2("\n---------------------------\n\n"); + } + + for (auto t : restart) { + start_timer(t->second, t->second.interval()); + } + + for (auto t : garbage) { + debug2("ERASE: Key: %i , id: %i, Type: %i \n", (uint32_t)t->first, t->second.id(), t->second.type()); + timers_.erase(t); + } + + if (timers_.empty()) + oneshot(1); - // All IRQ-handlers has to send EOI - IRQ_manager::eoi(0); + } -} -void PIT::init(){ - debug(" Initializing @ frequency: %16.16f MHz. Assigning myself to all timer interrupts.\n ", frequency()); - PIT::disable_regular_interrupts(); - IRQ_manager::enable_irq(0); -} + void PIT::init(){ + debug(" Initializing @ frequency: %16.16f MHz. Assigning myself to all timer interrupts.\n ", frequency()); + PIT::disable_regular_interrupts(); + IRQ_manager::enable_irq(0); + } -void PIT::set_mode(Mode mode){ - // Channel is the last two bits in the PIT mode register - // ...we always use channel 0 - auto channel = 0x00; - uint8_t config = mode | LO_HI | channel; - debug(" Setting mode %#x, config: %#x \n", mode, config); + void PIT::set_mode(Mode mode){ + // Channel is the last two bits in the PIT mode register + // ...we always use channel 0 + auto channel = 0x00; + uint8_t config = mode | LO_HI | channel; + debug(" Setting mode %#x, config: %#x \n", mode, config); - hw::outb(PIT_mode_register, config); - current_mode_ = mode; + hw::outb(PIT_mode_register, config); + current_mode_ = mode; -} + } -void PIT::set_freq_divider(uint16_t freq_divider){ - union{ - uint16_t whole; - uint8_t part[2]; - }data{freq_divider}; + void PIT::set_freq_divider(uint16_t freq_divider){ + union{ + uint16_t whole; + uint8_t part[2]; + }data{freq_divider}; - // Send frequency hi/lo to PIT - hw::outb(PIT_chan0, data.part[0]); - hw::outb(PIT_chan0, data.part[1]); + // Send frequency hi/lo to PIT + hw::outb(PIT_chan0, data.part[0]); + hw::outb(PIT_chan0, data.part[1]); - current_freq_divider_ = freq_divider; + current_freq_divider_ = freq_divider; -} + } -void PIT::oneshot(uint16_t t){ + void PIT::oneshot(uint16_t t){ - // Enable 1-shot mode - set_mode(ONE_SHOT); + // Enable 1-shot mode + set_mode(ONE_SHOT); - // Set a frequency for shot - set_freq_divider(t); -} + // Set a frequency for shot + set_freq_divider(t); + } } //< namespace hw diff --git a/src/hw/serial.cpp b/src/hw/serial.cpp new file mode 100644 index 0000000000..c42fddb813 --- /dev/null +++ b/src/hw/serial.cpp @@ -0,0 +1,112 @@ +// This file is a part of the IncludeOS unikernel - www.includeos.org +// +// Copyright 2015 Oslo and Akershus University College of Applied Sciences +// and Alfred Bratterud +// +// 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. + +#include + +#undef DEBUG + +using namespace hw; + +// Storage for port numbers +constexpr uint16_t Serial::ports_[]; + + +void Serial::init(){ + hw::outb(port_ + 1, 0x00); // Disable all interrupts + hw::outb(port_ + 3, 0x80); // Enable DLAB (set baud rate divisor) + hw::outb(port_ + 0, 0x03); // Set divisor to 3 (lo byte) 38400 baud + hw::outb(port_ + 1, 0x00); // (hi byte) + hw::outb(port_ + 3, 0x03); // 8 bits, no parity, one stop bit + hw::outb(port_ + 2, 0xC7); // Enable FIFO, clear them, with 14-byte threshold + hw::outb(port_ + 4, 0x0B); // IRQs enabled, RTS/DSR set +} + +Serial::Serial(int port) : + nr_{port}, + port_{ port < 5 ? ports_[port -1] : 0 } +{ + //init(); +} + +void Serial::on_data(on_data_handler del){ + enable_interrupt(); + on_data_=del; + IRQ_manager::subscribe(irq_, irq_delg::from(this) ); + INFO("Serial", "Subscribing to IRQ %i \n",irq_); +} + +void Serial::on_readline(on_string_handler del, char delim){ + newline = delim; + on_readline_ = del; + on_data(on_data_handler::from(this)); + INFO("Serial::on_readline", "Subscribing to data %i \n",irq_); +} + + +void Serial::enable_interrupt(){ + outb(port_ + 1, 0x01); + IRQ_manager::eoi(irq_); +} + +void Serial::disable_interrupt(){ + outb(port_ + 1, 0x00); +} + +char Serial::read(){ + return hw::inb(port_); +} + +void Serial::write(char c){ + while (is_transmit_empty() == 0); + hw::outb(port_, c); +} + +int Serial::received(){ + return hw::inb(port_ + 5) & 1; +} + +int Serial::is_transmit_empty() { + return hw::inb(port_ + 5) & 0x20; +} + + +void Serial::irq_handler_ () { + IRQ_manager::eoi(irq_); + + while (received()) + on_data_(read()); + +} + + +void Serial::readline_handler_ (char c) { + + if (c != newline) { + buf += c; + write(c); + return; + } + +#ifdef DEBUG + for ( auto& ch : buf ) + debug(" 0x%x | ", ch); +#endif + + // Call the event handler + on_readline_(buf); + buf.clear(); +} diff --git a/src/include/hw/cpu_freq_sampling.hpp b/src/include/hw/cpu_freq_sampling.hpp index 733dde58df..44b902a251 100644 --- a/src/include/hw/cpu_freq_sampling.hpp +++ b/src/include/hw/cpu_freq_sampling.hpp @@ -21,17 +21,17 @@ namespace hw { -/** Proper IRQ-handler for CPU frequency sampling - implemented in interrupts.s - @Note - PIT::estimateCPUFrequency() will register- /de-register this as needed */ -extern "C" void cpu_sampling_irq_handler(); + /** Proper IRQ-handler for CPU frequency sampling - implemented in interrupts.s + @Note + PIT::estimateCPUFrequency() will register- /de-register this as needed */ + extern "C" void cpu_sampling_irq_handler(); -/** CPU frequency sampling. Implemented in hw/cpu_freq_sampling.cpp - @Note this will be automatically called by the oirq-handler */ -extern "C" void cpu_sampling_irq_entry(); + /** CPU frequency sampling. Implemented in hw/cpu_freq_sampling.cpp + @Note this will be automatically called by the oirq-handler */ + extern "C" void cpu_sampling_irq_entry(); -extern "C" void irq_32_entry(); + extern "C" void irq_32_entry(); -extern "C" MHz calculate_cpu_frequency(); + extern "C" MHz calculate_cpu_frequency(); } //< namespace hw diff --git a/src/kernel/cpuid.cpp b/src/kernel/cpuid.cpp index 7982f55c3d..d5b9ba34d6 100644 --- a/src/kernel/cpuid.cpp +++ b/src/kernel/cpuid.cpp @@ -95,32 +95,32 @@ static cpuid_t cpuid_info(const unsigned int func, const unsigned int subfunc) { cpuid_t info; __asm__ __volatile__ ( - "cpuid" - : "=a"(info.EAX), "=b"(info.EBX), "=c"(info.ECX), "=d"(info.EDX) - : "a"(func), "c"(subfunc) : "%eax", "%ebx", "%ecx", "%edx" - ); + "cpuid" + : "=a"(info.EAX), "=b"(info.EBX), "=c"(info.ECX), "=d"(info.EDX) + : "a"(func), "c"(subfunc) : "%eax", "%ebx", "%ecx", "%edx" + ); return info; } bool CPUID::isAmdCpu() { cpuid_t info = cpuid_info(0, 0); if (memcmp((char *) (&info.EBX), "htuA", 4) == 0 - && memcmp((char *) (&info.EDX), "itne", 4) == 0 - && memcmp((char *) (&info.ECX), "DMAc", 4) == 0) - { + && memcmp((char *) (&info.EDX), "itne", 4) == 0 + && memcmp((char *) (&info.ECX), "DMAc", 4) == 0) + { return true; - } + } return false; } bool CPUID::isIntelCpu() { cpuid_t info = cpuid_info(0, 0); if (memcmp((char *) (&info.EBX), "Genu", 4) == 0 - && memcmp((char *) (&info.EDX), "ineI", 4) == 0 - && memcmp((char *) (&info.ECX), "ntel", 4) == 0) - { + && memcmp((char *) (&info.EDX), "ineI", 4) == 0 + && memcmp((char *) (&info.ECX), "ntel", 4) == 0) + { return true; - } + } return false; } diff --git a/src/kernel/interrupts.s b/src/kernel/interrupts.s index 0a4f4a69cf..971fb1113a 100644 --- a/src/kernel/interrupts.s +++ b/src/kernel/interrupts.s @@ -46,6 +46,8 @@ .global irq_15_entry .global cpu_sampling_irq_entry +.global ide_irq_entry +.extern ide_irq_handler /* @@ -84,37 +86,14 @@ IRQ irq_13_entry irq_13_handler IRQ irq_14_entry irq_14_handler IRQ irq_15_entry irq_15_handler +IRQ ide_irq_entry ide_irq_handler +IRQ cpu_sampling_irq_entry cpu_sampling_irq_handler +IRQ irq_default_entry irq_default_handler exception_13_entry: cli call exception_13_handler - -cpu_sampling_irq_entry: - cli - pusha - call cpu_sampling_irq_handler - popa - sti - iret - - -irq_default_entry: - cli - pusha - call irq_default_handler - popa - sti - iret - - -//Send EOI for the timer -// movb PIC_PORT, %al -// movw SIG_EOI, %dx -// outb %al, %dx - - - irq_timer_entry: cli diff --git a/src/kernel/irq_manager.cpp b/src/kernel/irq_manager.cpp index 198faba382..61496f1314 100644 --- a/src/kernel/irq_manager.cpp +++ b/src/kernel/irq_manager.cpp @@ -15,8 +15,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#define DEBUG // Enable debugging -#define DEBUG2 +//#define DEBUG // Enable debugging +//#define DEBUG2 #include @@ -26,17 +26,16 @@ #include #include -const int irq_base=32; unsigned int IRQ_manager::irq_mask {0xFFFB}; -IDTDescr IRQ_manager::idt[256] {}; +IDTDescr IRQ_manager::idt[irq_lines] {}; bool IRQ_manager::idt_is_set {false}; -irq_bitfield irq_pending {0}; -irq_bitfield IRQ_manager::irq_subscriptions {0}; +irq_bitfield IRQ_manager::irq_pending_ {0}; +irq_bitfield IRQ_manager::irq_subscriptions_ {0}; -void (*IRQ_manager::irq_subscribers[sizeof(irq_bitfield)*8])() {nullptr}; -IRQ_manager::irq_delegate IRQ_manager::irq_delegates[sizeof(irq_bitfield)*8]; +void (*IRQ_manager::irq_subscribers_[sizeof(irq_bitfield)*8])() {nullptr}; +IRQ_manager::irq_delegate IRQ_manager::irq_delegates_[sizeof(irq_bitfield)*8]; void IRQ_manager::enable_interrupts() { __asm__ volatile("sti"); @@ -50,9 +49,9 @@ enum { static inline void cpuid(int code, uint32_t *a, uint32_t *d) { asm volatile("cpuid" - :"=a"(*a),"=d"(*d) - :"a"(code) - :"ecx","ebx"); + :"=a"(*a),"=d"(*d) + :"a"(code) + :"ecx","ebx"); } bool cpuHasAPIC() { @@ -67,39 +66,39 @@ extern "C" } /** Default Exception-handler, which just prints its number */ -#define EXCEPTION_HANDLER(I) \ - void exception_##I##_handler() { \ - printf("\n\n>>>> !!! CPU EXCEPTION %i !!! <<<<<\n", I); \ - printf("Heap end: %#x \n", (uint32_t)&_end); \ - kill(1, 9); \ +#define EXCEPTION_HANDLER(I) \ + void exception_##I##_handler() { \ + printf("\n\n>>>> !!! CPU EXCEPTION %i !!! <<<<<\n", I); \ + printf("Heap end: %#x \n", (uint32_t)&_end); \ + kill(1, 9); \ } void exception_handler() { - #define frp(N, ra) \ - (__builtin_frame_address(N) != nullptr) && \ - (ra = __builtin_return_address(N)) != nullptr +#define frp(N, ra) \ + (__builtin_frame_address(N) != nullptr) && \ + (ra = __builtin_return_address(N)) != nullptr printf("\n"); - #define PRINT_TRACE(N, ra) \ - printf("[%d] Return %p\n", N, ra); +#define PRINT_TRACE(N, ra) \ + printf("[%d] Return %p\n", N, ra); void* ra; if (frp(0, ra)) { - PRINT_TRACE(0, ra); - if (frp(1, ra)) { - PRINT_TRACE(1, ra); - if (frp(2, ra)) { - PRINT_TRACE(2, ra); - if (frp(3, ra)) { - PRINT_TRACE(3, ra); - if (frp(4, ra)) { - PRINT_TRACE(4, ra); - if (frp(5, ra)) { - PRINT_TRACE(5, ra); - if (frp(6, ra)) - PRINT_TRACE(6, ra); - }}}}}} + PRINT_TRACE(0, ra); + if (frp(1, ra)) { + PRINT_TRACE(1, ra); + if (frp(2, ra)) { + PRINT_TRACE(2, ra); + if (frp(3, ra)) { + PRINT_TRACE(3, ra); + if (frp(4, ra)) { + PRINT_TRACE(4, ra); + if (frp(5, ra)) { + PRINT_TRACE(5, ra); + if (frp(6, ra)) + PRINT_TRACE(6, ra); + }}}}}} printf(">>>> !!! CPU EXCEPTION !!! <<<<\n"); extern char _end; @@ -113,29 +112,22 @@ void exception_handler() * - Set pending flag * - Increment counter */ -static uint32_t __irqueues[256] {0}; - -#define IRQ_HANDLER(I) \ - void irq_##I##_handler() { \ - irq_pending |= (1 << I); \ - __sync_fetch_and_add(&__irqueues[I],1); \ - debug(" IRQ %i Pending: 0x%ix. Count: %i\n", I, \ - irq_pending, __irqueues[I]); \ - } +uint32_t IRQ_manager::irq_counters_[32] {0}; -//debug(" IRQ %i. Pending: 0x%lx\n",I,irq_pending); -// The delegates will handle EOI -// eoi(I-IRQ_BASE); +#define IRQ_HANDLER(I) \ + void irq_##I##_handler() { \ + IRQ_manager::register_interrupt(I); \ + } /* Macro magic to register default gates */ -#define REG_DEFAULT_EXCPT(I) create_gate(&(idt[I]),exception_entry, \ - default_sel, default_attr ); +#define REG_DEFAULT_EXCPT(I) create_gate(&(idt[I]),exception_entry, \ + default_sel, default_attr ); -#define REG_DEFAULT_IRQ(I) create_gate(&(idt[I + irq_base]),irq_##I##_entry, \ - default_sel, default_attr ); +#define REG_DEFAULT_IRQ(I) create_gate(&(idt[I + irq_base]),irq_##I##_entry, \ + default_sel, default_attr ); - /* EXCEPTIONS */ +/* EXCEPTIONS */ #define EXCEPTION_PAIR(I) void exception_entry(); #define IRQ_PAIR(I) void irq_##I##_entry(); IRQ_HANDLER(I) @@ -150,8 +142,6 @@ static uint32_t __irqueues[256] {0}; */ extern "C"{ void _irq_20_entry(int i); - //Array of custom IRQ-handlers - void (*custom_handlers[256])(); void irq_default_handler(); void irq_default_entry(); @@ -194,50 +184,42 @@ void IRQ_manager::init() //Create an idt entry for the 'lidt' instruction idt_loc idt_reg; - idt_reg.limit = (256*sizeof(IDTDescr))-1; + idt_reg.limit = (irq_lines*sizeof(IDTDescr))-1; idt_reg.base = (uint32_t)idt; INFO("IRQ manager", "Creating interrupt handlers"); // Assign the lower 32 IRQ's : Exceptions REG_DEFAULT_EXCPT(0) REG_DEFAULT_EXCPT(1) REG_DEFAULT_EXCPT(2) - REG_DEFAULT_EXCPT(3) REG_DEFAULT_EXCPT(4) REG_DEFAULT_EXCPT(5) - REG_DEFAULT_EXCPT(6) REG_DEFAULT_EXCPT(7) REG_DEFAULT_EXCPT(8) - REG_DEFAULT_EXCPT(9) REG_DEFAULT_EXCPT(10) REG_DEFAULT_EXCPT(11) - REG_DEFAULT_EXCPT(12) REG_DEFAULT_EXCPT(13) REG_DEFAULT_EXCPT(14) - REG_DEFAULT_EXCPT(15) REG_DEFAULT_EXCPT(16) REG_DEFAULT_EXCPT(17) - REG_DEFAULT_EXCPT(18) REG_DEFAULT_EXCPT(19) REG_DEFAULT_EXCPT(20) - // GATES 21-29 are reserved - REG_DEFAULT_EXCPT(30) REG_DEFAULT_EXCPT(31) + REG_DEFAULT_EXCPT(3) REG_DEFAULT_EXCPT(4) REG_DEFAULT_EXCPT(5) + REG_DEFAULT_EXCPT(6) REG_DEFAULT_EXCPT(7) REG_DEFAULT_EXCPT(8) + REG_DEFAULT_EXCPT(9) REG_DEFAULT_EXCPT(10) REG_DEFAULT_EXCPT(11) + REG_DEFAULT_EXCPT(12) REG_DEFAULT_EXCPT(13) REG_DEFAULT_EXCPT(14) + REG_DEFAULT_EXCPT(15) REG_DEFAULT_EXCPT(16) REG_DEFAULT_EXCPT(17) + REG_DEFAULT_EXCPT(18) REG_DEFAULT_EXCPT(19) REG_DEFAULT_EXCPT(20) + // GATES 21-29 are reserved + REG_DEFAULT_EXCPT(30) REG_DEFAULT_EXCPT(31) + INFO2("+ Exception gates set for irq < 32"); //Redirected IRQ 0 - 15 REG_DEFAULT_IRQ(0) REG_DEFAULT_IRQ(1) REG_DEFAULT_IRQ(3) - REG_DEFAULT_IRQ(4) REG_DEFAULT_IRQ(5) REG_DEFAULT_IRQ(6) - REG_DEFAULT_IRQ(7) REG_DEFAULT_IRQ(8) REG_DEFAULT_IRQ(9) - REG_DEFAULT_IRQ(10) REG_DEFAULT_IRQ(11) REG_DEFAULT_IRQ(12) - REG_DEFAULT_IRQ(13) REG_DEFAULT_IRQ(14) REG_DEFAULT_IRQ(15) - - // Default gates for "real IRQ lines", 32-64 - INFO2("+ Exception gates set for irq < 32"); - - //Set all irq-gates (>= 44) to the default handler - for(int i=48;i<256;i++){ - create_gate(&(idt[i]),irq_default_entry,default_sel,default_attr); - } + REG_DEFAULT_IRQ(4) REG_DEFAULT_IRQ(5) REG_DEFAULT_IRQ(6) + REG_DEFAULT_IRQ(7) REG_DEFAULT_IRQ(8) REG_DEFAULT_IRQ(9) + REG_DEFAULT_IRQ(10) REG_DEFAULT_IRQ(11) REG_DEFAULT_IRQ(12) + REG_DEFAULT_IRQ(13) REG_DEFAULT_IRQ(14) REG_DEFAULT_IRQ(15) + + //Set all irq-gates (> 47) to the default handler + for(int i=48;i= 32"); - //Load IDT __asm__ volatile ("lidt %0": :"m"(idt_reg) ); //Initialize the interrupt controller hw::PIC::init(); - - enable_irq(2); //Slave PIC irq enable_interrupts(); - - //Test zero-division exception - //int i=0; float x=1/i; printf("ERROR: 1/0 == %f \n",x); } // A union to be able to extract the lower and upper part of an address @@ -250,9 +232,9 @@ union addr_union { }; void IRQ_manager::create_gate(IDTDescr* idt_entry, - void (*function_addr)(), - uint16_t segment_sel, - char attributes) { + void (*function_addr)(), + uint16_t segment_sel, + char attributes) { addr_union addr; addr.whole = (uint32_t)function_addr; idt_entry->offset_1 = addr.lo16; @@ -270,7 +252,7 @@ void IRQ_manager::set_handler(uint8_t irq, void(*function_addr)()) { * previous interrupts won't have reported EOI and new handler * will never get called */ - eoi(irq); + eoi(irq - irq_base); } void (*IRQ_manager::get_handler(uint8_t irq))() { @@ -282,11 +264,11 @@ void (*IRQ_manager::get_handler(uint8_t irq))() { } IRQ_manager::irq_delegate IRQ_manager::get_subscriber(uint8_t irq) { - return irq_delegates[irq]; + return irq_delegates_[irq]; } void IRQ_manager::enable_irq(uint8_t irq) { - hw::PIC::enable_irq(irq); + hw::PIC::enable_irq(irq); } int IRQ_manager::timer_interrupts {0}; @@ -302,14 +284,14 @@ void IRQ_manager::subscribe(uint8_t irq, irq_delegate del) { //void(*notify)() enable_irq(irq); // Mark IRQ as subscribed to - irq_subscriptions |= (1 << irq); + irq_subscriptions_ |= (1 << irq); // Add callback to subscriber list (for now overwriting any previous) //irq_subscribers[irq] = notify; - irq_delegates[irq] = del; + irq_delegates_[irq] = del; eoi(irq); - INFO("IRQ manager", "Updated subscriptions: %#x irq: %i", irq_subscriptions, irq); + INFO("IRQ manager", "Updated subscriptions: %#x irq: %i", irq_subscriptions_, irq); } /** Get most significant bit of b. */ @@ -323,42 +305,44 @@ void IRQ_manager::notify() { //__asm__("cli"); // Get the IRQ's that are both pending and subscribed to - irq_bitfield todo {static_cast(irq_subscriptions & irq_pending)}; + irq_bitfield todo {static_cast(irq_subscriptions_ & irq_pending_)}; int irq {0}; while (todo) { - // Select the first IRQ to notify - irq = bsr(todo); + + // Select the first IRQ to notify - the least significant bit set + // - lowesr bit/IRQ, means higher priority + irq = __builtin_ffs(todo) - 1; // Notify - debug2(" __irqueue %i Count: %i\n", irq, __irqueues[irq]); - irq_delegates[irq](); + debug2(" __irqueue %i Count: %i\n", irq, irq_counters_[irq]); + irq_delegates_[irq](); // Decrement the counter - __sync_fetch_and_sub(&__irqueues[irq], 1); + __sync_fetch_and_sub(&irq_counters_[irq], 1); // Critical section start // Spinlock? Well, we can't lock out the IRQ-handler // ... and we don't have a timer interrupt so we can't do blocking locks. - if (!__irqueues[irq]) { - // Remove the IRQ from pending list - irq_pending &= ~(1 << irq); - //debug(" IRQ's pending: 0x%lx\n",irq_pending); + if (!irq_counters_[irq]) { + // Remove the IRQ from pending list + irq_pending_ &= ~(1 << irq); + //debug(" IRQ's pending: 0x%lx\n",irq_pending_); } // Critical section end // Find remaining IRQ's both pending and subscribed to - todo = (irq_subscriptions & irq_pending); + todo = (irq_subscriptions_ & irq_pending_); } //hlt - debug(" Done. OS going to sleep.\n"); + debug2(" Done. OS going to sleep.\n"); //__asm__("sti"); __asm__ volatile("hlt;"); } void IRQ_manager::eoi(uint8_t irq) { - hw::PIC::eoi(irq); + hw::PIC::eoi(irq); } void irq_default_handler() { @@ -381,7 +365,7 @@ void irq_timer_handler() { } inline void disable_pic() { - asm volatile("mov $0xff,%al; " \ - "out %al,$0xa1; " \ - "out %al,$0x21; "); + asm volatile("mov $0xff,%al; " \ + "out %al,$0xa1; " \ + "out %al,$0x21; "); } diff --git a/src/kernel/kernel_start.cpp b/src/kernel/kernel_start.cpp index 4d775ba328..fe3cf58222 100644 --- a/src/kernel/kernel_start.cpp +++ b/src/kernel/kernel_start.cpp @@ -16,20 +16,13 @@ // limitations under the License. //#define DEBUG -#include #include #include -#include extern "C" { - extern void _init_c_runtime(); - static void init_serial(); - -#ifdef DEBUG - static const int _test_glob = 123; - static int _test_constructor = 0; -#endif + extern uintptr_t __stack_chk_guard; + void _init_c_runtime(); // enables Streaming SIMD Extensions static void enableSSE(void) @@ -44,64 +37,26 @@ extern "C" __asm__ ("mov %eax, %cr4"); } -#ifdef DEBUG - __attribute__((constructor)) void test_constr() - { - OS::rsprint("\t * C constructor was called!\n"); - _test_constructor = 1; + static char __attribute__((noinline)) + stack_smasher(const char* src) { + char bullshit[16]; + + for (int i = -100; i < 100; i++) + strcpy(bullshit+i, src); + + return bullshit[15]; } -#endif - void _start(void) - { - __asm__ volatile ("cli"); - + void _start(void) { // enable SSE extensions bitmask in CR4 register enableSSE(); - // init serial port - init_serial(); + //stack_smasher("1234567890 12345 hello world! test -.-"); // Initialize stack-unwinder, call global constructors etc. - #ifdef DEBUG - OS::rsprint("\t * Initializing C-environment... \n"); - #endif _init_c_runtime(); - FILLINE('='); - CAPTION("#include // Literally"); - FILLINE('='); - -// verify that global constructors were called -#ifdef DEBUG - assert(_test_glob == 123); - assert(_test_constructor == 1); -#endif // Initialize some OS functionality OS::start(); - - // Will only work if any destructors are called (I think?) - //_fini(); - } - - #define SERIAL_PORT 0x3f8 - void init_serial() { - hw::outb(SERIAL_PORT + 1, 0x00); // Disable all interrupts - hw::outb(SERIAL_PORT + 3, 0x80); // Enable DLAB (set baud rate divisor) - hw::outb(SERIAL_PORT + 0, 0x03); // Set divisor to 3 (lo byte) 38400 baud - hw::outb(SERIAL_PORT + 1, 0x00); // (hi byte) - hw::outb(SERIAL_PORT + 3, 0x03); // 8 bits, no parity, one stop bit - hw::outb(SERIAL_PORT + 2, 0xC7); // Enable FIFO, clear them, with 14-byte threshold - hw::outb(SERIAL_PORT + 4, 0x0B); // IRQs enabled, RTS/DSR set - } - - int is_transmit_empty() { - return hw::inb(SERIAL_PORT + 5) & 0x20; - } - - void write_serial(char a) { - while (is_transmit_empty() == 0); - - hw::outb(SERIAL_PORT, a); } } diff --git a/src/kernel/os.cpp b/src/kernel/os.cpp index dbd3cadd1b..e895ff1b07 100644 --- a/src/kernel/os.cpp +++ b/src/kernel/os.cpp @@ -6,9 +6,9 @@ // 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. @@ -21,65 +21,74 @@ #include #include #include - #include -// A private class to handle IRQ #include +#include #include #include bool OS::power_ {true}; -MHz OS::cpu_mhz_ {0}; +MHz OS::cpu_mhz_ {1000}; // Set default rsprint_handler OS::rsprint_func OS::rsprint_handler_ = &OS::default_rsprint; +hw::Serial& OS::com1 = hw::Serial::port<1>(); extern "C" uint16_t _cpu_sampling_freq_divider_; void OS::start() { + + // Initialize serial port + com1.init(); + + // Print a fancy header + FILLINE('='); + CAPTION("#include // Literally\n"); + FILLINE('='); + debug("\t[*] OS class started\n"); srand(time(NULL)); - + // Heap extern caddr_t heap_end; extern char _end; MYINFO("Heap start: @ %p", heap_end); MYINFO("Current end is: @ %p", &_end); - - // OS::rsprint("\t[*] IRQ handler\n"); - asm("cli"); + atexit(default_exit); + // Set up interrupt handlers IRQ_manager::init(); - + // Initialize the Interval Timer hw::PIT::init(); // Initialize PCI devices PCI_manager::init(); - asm("sti"); - - - MYINFO("Estimating CPU-frequency"); - INFO2("|"); - INFO2("+--(10 samples, %f sec. interval)", - (hw::PIT::frequency() / _cpu_sampling_freq_divider_).count()); - INFO2("|"); - - // TODO: Debug why actual measurments sometimes causes problems. Issue #246. - cpu_mhz_ = MHz(2200); //hw::PIT::CPUFrequency(); - - INFO2("+--> %f MHz", cpu_mhz_.count()); - + /** Estimate CPU frequency + + MYINFO("Estimating CPU-frequency"); + INFO2("|"); + INFO2("+--(10 samples, %f sec. interval)", + (hw::PIT::frequency() / _cpu_sampling_freq_divider_).count()); + INFO2("|"); + + // TODO: Debug why actual measurments sometimes causes problems. Issue #246. + cpu_mhz_ = hw::PIT::CPUFrequency(); + + INFO2("+--> %f MHz", cpu_mhz_.count()); + + **/ + MYINFO("Starting %s", Service::name().c_str()); FILLINE('='); // Everything is ready Service::start(); - + event_loop(); } @@ -96,12 +105,12 @@ void OS::event_loop() { printf(" IncludeOS %s\n", version().c_str()); printf(" +--> Running [ %s ]\n", Service::name().c_str()); FILLINE('~'); - + while (power_) { - IRQ_manager::notify(); + IRQ_manager::notify(); debug(" Woke up @ t = %li\n", uptime()); } - + //Cleanup //Service::stop(); } @@ -109,31 +118,21 @@ void OS::event_loop() { size_t OS::rsprint(const char* str) { size_t len = 0; - // Measure length + // Measure length while (str[len++]); - + // Output callback rsprint_handler_(str, len); return len; } size_t OS::rsprint(const char* str, const size_t len) { - // Output callback OS::rsprint_handler_(str, len); - return len; -} - -/* STEAL: Print to serial port 0x3F8 */ -void OS::rswrite(const char c) { - /* Wait for the previous character to be sent */ - while ((hw::inb(0x3FD) & 0x20) != 0x20); - - /* Send the character */ - hw::outb(0x3F8, c); + return len; } void OS::default_rsprint(const char* str, size_t len) { for(size_t i = 0; i < len; ++i) - rswrite(str[i]); + com1.write(str[i]); } diff --git a/src/kernel/pci_manager.cpp b/src/kernel/pci_manager.cpp index a0998e590f..e643043ea4 100644 --- a/src/kernel/pci_manager.cpp +++ b/src/kernel/pci_manager.cpp @@ -35,11 +35,11 @@ void PCI_manager::init() { uint32_t id {PCI::WTF}; for (uint16_t pci_addr {0}; pci_addr < 255; ++pci_addr) { - id = hw::PCI_Device::read_dword(pci_addr, PCI::CONFIG_VENDOR); - if (id != PCI::WTF) { - hw::PCI_Device dev {pci_addr, id}; - devices_[dev.classcode()].emplace_back(dev); - } + id = hw::PCI_Device::read_dword(pci_addr, PCI::CONFIG_VENDOR); + if (id != PCI::WTF) { + hw::PCI_Device dev {pci_addr, id}; + devices_[dev.classcode()].emplace_back(dev); + } } // Pretty printing, end of device tree diff --git a/src/kernel/rdrand.cpp b/src/kernel/rdrand.cpp index a24ece1541..aa927244c5 100644 --- a/src/kernel/rdrand.cpp +++ b/src/kernel/rdrand.cpp @@ -25,9 +25,9 @@ bool rdrand16(uint16_t* result) { int res = 0; while (res == 0) - { - res = _rdrand16_step(result); - } + { + res = _rdrand16_step(result); + } return (res == 1); } @@ -35,8 +35,8 @@ bool rdrand32(uint32_t* result) { int res = 0; while (res == 0) - { - res = _rdrand32_step(result); - } + { + res = _rdrand32_step(result); + } return (res == 1); } diff --git a/src/kernel/syscalls.cpp b/src/kernel/syscalls.cpp index 112cd976b1..27d8d4549b 100644 --- a/src/kernel/syscalls.cpp +++ b/src/kernel/syscalls.cpp @@ -6,9 +6,9 @@ // 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. @@ -33,36 +33,33 @@ static bool debug_syscalls {true}; caddr_t heap_end; + void _exit(int status) { - printf("\tSYSCALL EXIT: status %d. Nothing more we can do.\n", status); - printf("\tSTOPPING EXECUTION\n"); - while (1) - asm("cli; hlt;"); + (void) status; + panic("Exit called"); } -int close(int UNUSED(file)) { - debug("SYSCALL CLOSE Dummy, returning -1"); +int close(int) { + panic("SYSCALL CLOSE NOT SUPPORTED"); return -1; }; -int execve(const char* UNUSED(name), - char* const* UNUSED(argv), +int execve(const char* UNUSED(name), + char* const* UNUSED(argv), char* const* UNUSED(env)) { - debug((char*) "SYSCALL EXECVE NOT SUPPORTED"); - errno = ENOMEM; + panic("SYSCALL EXECVE NOT SUPPORTED"); return -1; }; int fork() { - debug("SYSCALL FORK NOT SUPPORTED"); - errno=ENOMEM; + panic("SYSCALL FORK NOT SUPPORTED"); return -1; }; int fstat(int UNUSED(file), struct stat *st) { debug("SYSCALL FSTAT Dummy, returning OK 0"); - st->st_mode = S_IFCHR; + st->st_mode = S_IFCHR; return 0; }; @@ -78,40 +75,39 @@ int isatty(int file) { } // Not stdxxx, error out - debug("SYSCALL ISATTY Unknown descriptor %i", file); + panic("SYSCALL ISATTY Unknown descriptor "); errno = EBADF; return 0; } int link(const char* UNUSED(old), const char* UNUSED(_new)) { - debug("SYSCALL LINK - Unsupported"); - kill(1,9); + panic("SYSCALL LINK unsupported"); return -1; } int unlink(const char* UNUSED(name)) { - debug((char*)"SYSCALL UNLINK Dummy, returning -1"); + panic("SYSCALL UNLINK unsupported"); return -1; } off_t lseek(int UNUSED(file), off_t UNUSED(ptr), int UNUSED(dir)) { - debug("SYSCALL LSEEK returning 0"); + panic("SYSCALL LSEEK returning 0"); return 0; } int open(const char* UNUSED(name), int UNUSED(flags), ...) { - debug("SYSCALL OPEN Dummy, returning -1"); + panic("SYSCALL OPEN unsupported"); return -1; }; int read(int UNUSED(file), void* UNUSED(ptr), size_t UNUSED(len)) { - debug("SYSCALL READ Not supported, returning 0"); + panic("SYSCALL READ unsupported"); return 0; } int write(int file, const void* ptr, size_t len) { if (file == syscall_fd and not debug_syscalls) { - return len; + return len; } return OS::rsprint((const char*) ptr, len); } @@ -130,7 +126,7 @@ int stat(const char* UNUSED(file), struct stat *st) { }; clock_t times(struct tms* UNUSED(buf)) { - debug((char*)"SYSCALL TIMES Dummy, returning -1"); + panic("SYSCALL TIMES Dummy, returning -1"); return -1; }; @@ -147,13 +143,13 @@ int gettimeofday(struct timeval* p, void* UNUSED(z)) { return 5; } -int kill(pid_t pid, int sig) { +int kill(pid_t pid, int sig) { printf("!!! Kill PID: %i, SIG: %i - %s ", pid, sig, strsignal(sig)); - + if (sig == 6ul) { printf("/ ABORT\n"); } - + panic("\tKilling a process doesn't make sense in IncludeOS. Panic."); errno = ESRCH; return -1; @@ -164,9 +160,14 @@ void panic(const char* why) { printf("\n\t **** PANIC: ****\n %s\n", why); printf("\tHeap end: %p\n", heap_end); while(1) __asm__("cli; hlt;"); - } +// No continuation from here +void default_exit() { + panic("Exit was called"); +} + + // To keep our sanity, we need a reason for the abort void abort_ex(const char* why) { printf("\n\t !!! abort_ex. Why: %s", why); diff --git a/src/kernel/terminal.cpp b/src/kernel/terminal.cpp new file mode 100644 index 0000000000..f2b86be68c --- /dev/null +++ b/src/kernel/terminal.cpp @@ -0,0 +1,303 @@ +// This file is a part of the IncludeOS unikernel - www.includeos.org +// +// Copyright 2015 Oslo and Akershus University College of Applied Sciences +// and Alfred Bratterud +// +// 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. + +#include +#include + +#include +#include +#include + +Terminal::Terminal() + : iac(false), newline(false), subcmd(0) +{ + add_basic_commands(); +} + +Terminal::Terminal(Connection_ptr csock) + : Terminal() +{ + csock->read(1024, [this](auto buffer, size_t n){ + this->read((char*)buffer.get(), n); + }); + /*csock->onReceive( + [this] (auto conn, bool) + { + char buffer[1024]; + size_t bytes = conn->read(buffer, sizeof(buffer)); + + this->read(buffer, bytes); + });*/ + + on_write = + [csock] (const char* buffer, size_t len) + { + csock->write(buffer, len); + }; + + on_exit = + [csock] { + csock->close(); + }; + // after setting up everything, show introduction + intro(); +} + +Terminal::Terminal(hw::Serial& serial) + : Terminal() +{ + serial.on_data( + [this, &serial] (char c) + { + this->read(&c, 1); + if (c == CR) + { + c = LF; + this->read(&c, 1); + } + else + { + serial.write(c); + } + }); + + on_write = + [&serial] (const char* buffer, size_t len) + { + for (size_t i = 0; i < len; i++) + serial.write(buffer[i]); + }; + + on_exit = + [] { + // do nothing + }; + // after setting up everything, show introduction + intro(); +} + +void Terminal::read(const char* buf, size_t len) +{ + while (len) + { + if (this->subcmd) + { + // execute options + option(this->subcmd, (uint8_t) *buf); + this->subcmd = 0; + } + else if (this->iac) + { + command(*(uint8_t*) buf); + this->iac = false; + } + else if (*buf == 13 && !newline) + { + newline = true; + } + else if (*buf == 10 && newline) + { + newline = false; + // parse message + run(buffer); + buffer.clear(); + } + else if (*buf == 0) + { + // NOP + } + else if ((uint8_t) *buf == 0xFF) + { + // Interpret as Command + this->iac = true; + } + else + { + buffer.append(buf, 1); + } + buf++; len--; + } +} + +void Terminal::command(uint8_t cmd) +{ + switch (cmd) + { + case 247: // erase char + printf("CMD: Erase char\n"); + break; + case 248: // erase line + printf("CMD: Erase line\n"); + break; + case 250: // Begin + printf("CMD: Begin...\n"); + break; + case 251: // Will USE + case 252: // Won't USE + case 253: // Start USE + case 254: // Stop USE + //printf("CMD: Command %d\n", cmd); + this->subcmd = cmd; + break; + case 255: + //printf("CMD: Command %d\n", cmd); + this->subcmd = cmd; + break; + default: + printf("CMD: Unknown command %d\n", cmd); + } +} + +void Terminal::option(uint8_t option, uint8_t cmd) +{ + (void) option; + switch (cmd) + { + case 24: // terminal type + break; + default: + //printf("CMD: Unknown cmd %d for option %d\n", cmd, option); + break; + } +} + +std::vector +split(const std::string& text, std::string& command) +{ + std::vector retv; + size_t x = 0; + size_t p = 0; + // ignore empty messages + if (text.empty()) return retv; + // extract command + { + x = text.find(" "); + // early return for cmd-only msg + if (x == std::string::npos) + { + command = text; + return retv; + } + // command is substring + command = text.substr(0, x); + p = x+1; + } + // parse remainder + do + { + x = text.find(" ", p+1); + size_t y = text.find(":", x+1); // find last param + + if (y == x+1) + { + // single argument + retv.push_back(text.substr(p, x-p)); + // ending text argument + retv.push_back(text.substr(y+1)); + break; + } + else if (x != std::string::npos) + { + // single argument + retv.push_back(text.substr(p, x-p)); + } + else + { + // last argument + retv.push_back(text.substr(p)); + } + p = x+1; + + } while (x != std::string::npos); + + return retv; +} + +void Terminal::run(const std::string& cmd_string) +{ + std::string cmd_name; + auto cmd_vec = split(cmd_string, cmd_name); + if (cmd_name.size()) + { + printf("Terminal::run(): %s\n", cmd_name.c_str()); + + auto it = commands.find(cmd_name); + if (it != commands.end()) + { + int retv = it->second.main(cmd_vec); + if (retv) write("%s returned: %d\r\n", cmd_name.c_str(), retv); + } + else + { + write("No such command: '%s'\r\n", cmd_name.c_str()); + } + } + prompt(); +} + +void Terminal::add_basic_commands() +{ + // ?: + add( + "?", "List available commands", + [this] (const std::vector&) -> int + { + for (auto cmd : this->commands) + { + write("%s \t-- %s\r\n", cmd.first.c_str(), cmd.second.desc.c_str()); + } + return 0; + }); + // exit: + add( + "exit", "Close the terminal", + [this] (const std::vector&) -> int + { + this->on_exit(); + return 0; + }); + +} +void Terminal::intro() +{ + std::string banana = + R"baaa( + ____ ___ + | _ \ ___ _ _.' _ `. + _ | [_) )' _ `._ _ ___ ! \ | | (_) | _ +|:;.| _ <| (_) | \ | |' _ `| \| | _ | .:;| +| `.[_) ) _ | \| | (_) | | | | |.',..| +':. `. /| | | | | _ | |\ | | |.' :;::' +!::, `-!_| | | |\ | | | | | \ !_!.' ':;! +!::; ":;:!.!.\_!_!_!.!-'-':;:'' '''! +';:' `::;::;' '' ., . + `: .,. `' .::... . .::;::;' + `..:;::;:.. ::;::;:;:;, :;::;' + "-:;::;:;: ':;::;:'' ;.-' + ""`---...________...---'"" + + > Banana Terminal v1 < + )baaa"; + + write("%s", banana.c_str()); + prompt(); +} + +void Terminal::prompt() +{ + write("%s", "$ "); +} diff --git a/src/kernel/terminal_disk.cpp b/src/kernel/terminal_disk.cpp new file mode 100644 index 0000000000..3e1a0bf114 --- /dev/null +++ b/src/kernel/terminal_disk.cpp @@ -0,0 +1,159 @@ +// This file is a part of the IncludeOS unikernel - www.includeos.org +// +// Copyright 2015 Oslo and Akershus University College of Applied Sciences +// and Alfred Bratterud +// +// 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. + +#include +#include +#include + +using namespace fs; + +int target_directory(Disk_ptr disk, const Path& path) +{ + // avoid stat on root directory + if (path.empty()) return 0; + + std::string strpath(path.to_string()); + + auto& fs = disk->fs(); + auto ent = fs.stat(strpath); + + if (!ent.is_valid()) + return 1; + else if (!ent.is_dir()) + return 1; + else + return 0; +} + +void Terminal::add_disk_commands(Disk_ptr disk) +{ + auto curdir = std::make_shared ("/"); + + // add 'cd' command + add("cd", "Change current directory", + [this, curdir, disk] (const std::vector& args) -> int + { + // current directory, somehow... + std::string target = "/"; + if (!args.empty()) target = args[0]; + + Path path(*curdir); + path += target; + + int rv = target_directory(disk, path); + if (rv) + { + this->write("cd: %s: No such file or directory\r\n", target.c_str()); + return rv; + } + *curdir = path.to_string(); + return 0; + }); + // add 'ls' command + add("ls", "List files in a folder", + [this, curdir, disk] (const std::vector& args) -> int + { + // current directory, somehow... + Path path(*curdir); + if (!args.empty()) path += args[0]; + + int rv = target_directory(disk, path); + if (rv) + { + this->write("ls: %s: No such file or directory\r\n", path.to_string().c_str()); + return rv; + } + + std::string target = path.to_string(); + + auto& fs = disk->fs(); + auto list = fs.ls(target); + if (!list.error) + { + this->write("%s \t%s \t%s \t%s\r\n", + "Name", "Size", "Type", "Sector"); + for (auto& ent : *list.entries) + { + this->write("%s \t%llu \t%s \t%llu\r\n", + ent.name().c_str(), ent.size(), ent.type_string().c_str(), ent.block); + } + this->write("Total %u\r\n", list.entries->size()); + return 0; + } + else + { + this->write("Could not list %s\r\n", args[0].c_str()); + return 1; + } + }); + // add 'stat' command + add("stat", "List file information", + [this, disk] (const std::vector& args) -> int + { + if (!args.empty()) + { + auto& fs = disk->fs(); + auto ent = fs.stat(args[0]); + if (ent.is_valid()) + { + this->write("%s \t%s \t%s \t%s\r\n", + "Name", "Size", "Type", "Sector"); + this->write("%s \t%llu \t%s \t%llu\r\n", + ent.name().c_str(), ent.size(), ent.type_string().c_str(), ent.block); + return 0; + } + else + { + this->write("stat: Cannot stat '%s'\r\n", args[0].c_str()); + return 1; + } + } + else + { + this->write("%s\r\n", "stat: Not enough arguments"); + return 1; + } + }); + // add 'cat' command + add("cat", "Concatenate files and print", + [this, disk] (const std::vector& args) -> int + { + auto& fs = disk->fs(); + + for (const auto& file : args) + { + // get file information + auto ent = fs.stat(file); + if (!ent.is_valid()) + { + this->write("cat: '%s': No such file or directory\r\n", file.c_str()); + return 1; + } + // read file contents + auto buf = fs.read(ent, 0, ent.size()); + if (!buf.buffer) + { + this->write("cat: '%s': I/O error\r\n", file.c_str()); + return 1; + } + // write to terminal client + std::string buffer((char*) buf.buffer.get(), buf.len); + this->write("%s\r\n", buffer.c_str()); + } + return 0; + }); +} diff --git a/src/kernel/vga.cpp b/src/kernel/vga.cpp index b54004195d..117f819487 100644 --- a/src/kernel/vga.cpp +++ b/src/kernel/vga.cpp @@ -15,20 +15,20 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include #include - #include -#include - static uint16_t make_vgaentry(const char c, const uint8_t color) noexcept { - uint16_t c16 {static_cast(c)}; - uint16_t color16 {static_cast(color)}; + uint16_t c16 = c; + uint16_t color16 = color; return c16 | color16 << 8; } +const uint16_t ConsoleVGA::DEFAULT_ENTRY = + make_vgaentry(32, make_color(COLOR_LIGHT_GREY, COLOR_BLACK)); ConsoleVGA::ConsoleVGA() noexcept: - row{0}, column{0} +row{0}, column{0} { this->color = make_color(COLOR_WHITE, COLOR_BLACK); this->buffer = reinterpret_cast(0xB8000); @@ -40,13 +40,21 @@ void ConsoleVGA::setColor(const uint8_t color) noexcept { } void ConsoleVGA::putEntryAt(const char c, const uint8_t color, const size_t x, const size_t y) noexcept { - const size_t index {(y * VGA_WIDTH) + x}; - this->buffer[index] = make_vgaentry(c, color); + put(make_vgaentry(c, color), x, y); } void ConsoleVGA::putEntryAt(const char c, const size_t x, const size_t y) noexcept { - const size_t index {(y * VGA_WIDTH) + x}; - this->buffer[index] = make_vgaentry(c, this->color); + put(make_vgaentry(c, this->color), x, y); +} + +void ConsoleVGA::setCursorAt(const size_t x, const size_t y) noexcept { + this->column = x; + this->row = y; +} + +inline void ConsoleVGA::put(uint16_t entry, size_t x, size_t y) noexcept { + const size_t index = y * VGA_WIDTH + x; + this->buffer[index] = entry; } void ConsoleVGA::increment(const int step) noexcept { @@ -56,12 +64,9 @@ void ConsoleVGA::increment(const int step) noexcept { } } -void ConsoleVGA::newline() noexcept { - // Use whitespace to force blank the remainder of the line - while (this->column < VGA_WIDTH) { - putEntryAt(32, this->column++, this->row); - } +void ConsoleVGA::newline() noexcept { + // Reset back to left side this->column = 0; @@ -70,7 +75,6 @@ void ConsoleVGA::newline() noexcept { this->row--; unsigned total {VGA_WIDTH * (VGA_HEIGHT - 1)}; - __m128i scan; // Copy rows upwards @@ -80,7 +84,7 @@ void ConsoleVGA::newline() noexcept { } // Clear out the last row - scan = _mm_set1_epi16(32); + scan = _mm_set1_epi16(DEFAULT_ENTRY); for (size_t n {0}; n < VGA_WIDTH; n += 8) { _mm_store_si128(reinterpret_cast<__m128i*>(&buffer[total + n]), scan); @@ -93,7 +97,7 @@ void ConsoleVGA::clear() noexcept { this->column = 0; for (size_t x {0}; x < (VGA_WIDTH * VGA_HEIGHT); ++x) - buffer[x] = 32; + buffer[x] = DEFAULT_ENTRY; } void ConsoleVGA::write(const char c) noexcept { diff --git a/src/linker.ld b/src/linker.ld index a1f9c9c021..4d9a4c7f27 100644 --- a/src/linker.ld +++ b/src/linker.ld @@ -12,22 +12,25 @@ SECTIONS _TEXT_END_ = .; } - .init : - { + .init ALIGN(0x10) : { _INIT_START_ = .; *(.init) _INIT_END_ = .; } + .fini ALIGN(0x10) : { + /* we don't want destructors taking up space */ + *(.fini) + } /* Global constructor array */ - .ctors : + .ctors ALIGN(0x10) : { _GCONSTR_START = .; *(.ctors) _GCONSTR_END = .; LONG(0); } - .dtors : + .dtors ALIGN(0x10) : { *(.dtors) LONG(0); @@ -39,9 +42,6 @@ SECTIONS *(.rodata*) *(.gnu.linkonce.r*) _RODATA_END_ = .; - - /* this will make the image bigger, but maybe give it some speed too */ - /*. = ALIGN(4096);*/ } /* For stack unwinding (Required by exception handling) */ @@ -65,6 +65,12 @@ SECTIONS _DATA_END_ = .; } + .syms : + { + *(.symtab) + *(.strtab) + } + .memdisk : { _DISK_START_ = .; @@ -82,5 +88,6 @@ SECTIONS } _includeos = .; - _end = .; + . = ALIGN(4096); + _end = .; } diff --git a/src/net/arp.cpp b/src/net/arp.cpp deleted file mode 100644 index 46666b9eb0..0000000000 --- a/src/net/arp.cpp +++ /dev/null @@ -1,231 +0,0 @@ -// This file is a part of the IncludeOS unikernel - www.includeos.org -// -// Copyright 2015 Oslo and Akershus University College of Applied Sciences -// and Alfred Bratterud -// -// 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 DEBUG // Allow debugging -#define DEBUG2 // Allow debugging - -#include - -#include -#include -#include -#include - -namespace net { - -static void ignore(Packet_ptr UNUSED(pckt)) { - debug2(" linklayer> Empty handler - DROP!\n"); -} - - // Initialize -Arp::Arp(net::Inet& inet) noexcept: - inet_ {inet}, - mac_ (inet.link_addr()), - linklayer_out_ {ignore} -{} - -void Arp::bottom(Packet_ptr pckt) { - debug2(" got %i bytes of data\n", pckt->size()); - - header* hdr = reinterpret_cast(pckt->buffer()); - - debug2("Have valid cache? %s\n", is_valid_cached(hdr->sipaddr) ? "YES" : "NO"); - cache(hdr->sipaddr, hdr->shwaddr); - - switch(hdr->opcode) { - - case H_request: { - debug2("\t ARP REQUEST: "); - debug2("%s is looking for %s\n", - hdr->sipaddr.str().c_str(), - hdr->dipaddr.str().c_str()); - - if (hdr->dipaddr == inet_.ip_addr()) { - arp_respond(hdr); - } else { - debug2("\t NO MATCH for My IP (%s). DROP!\n", - inet_.ip_addr().str().c_str()); - } - break; - } - - case H_reply: { - debug2("\t ARP REPLY: %s belongs to %s\n", - hdr->sipaddr.str().c_str(), hdr->shwaddr.str().c_str()); - - auto waiting = waiting_packets_.find(hdr->sipaddr); - - if (waiting != waiting_packets_.end()) { - debug("Had a packet waiting for this IP. Sending\n"); - transmit(waiting->second); - waiting_packets_.erase(waiting); - } - break; - } - - default: - debug2("\t UNKNOWN OPCODE\n"); - break; - } //< switch(hdr->opcode) -} - -void Arp::cache(IP4::addr ip, Ethernet::addr mac) { - debug2("Caching IP %s for %s\n", ip.str().c_str(), mac.str().c_str()); - - auto entry = cache_.find(ip); - - if (entry != cache_.end()) { - debug2("Cached entry found: %s recorded @ %llu. Updating timestamp\n", - entry->second.mac_.str().c_str(), entry->second.timestamp_); - - // Update - entry->second.update(); - - } else { - cache_[ip] = mac; // Insert - } -} - -bool Arp::is_valid_cached(IP4::addr ip) { - auto entry = cache_.find(ip); - - if (entry != cache_.end()) { - debug("Cached entry, mac: %s time: %llu Expiry: %llu\n", - entry->second.mac_.str().c_str(), - entry->second.timestamp_, entry->second.timestamp_ + cache_exp_t_); - debug("Time now: %llu\n", static_cast(OS::uptime())); - } - - return entry != cache_.end() - and (entry->second.timestamp_ + cache_exp_t_ > static_cast(OS::uptime())); -} - -extern "C" { - unsigned long ether_crc(int length, unsigned char *data); -} - -void Arp::arp_respond(header* hdr_in) { - debug2("\t IP Match. Constructing ARP Reply\n"); - - // Populate ARP-header - auto res = std::static_pointer_cast(inet_.createPacket(sizeof(header))); - res->init(mac_, inet_.ip_addr()); - - res->set_dest_mac(hdr_in->shwaddr); - res->set_dest_ip(hdr_in->sipaddr); - res->set_opcode(H_reply); - - debug2("\t My IP: %s belongs to My Mac: %s\n", - res->source_ip().str().c_str(), res->source_mac().str().c_str()); - - linklayer_out_(res); -} - -void Arp::transmit(Packet_ptr pckt) { - assert(pckt->size()); - - /** Get destination IP from IP header */ - IP4::ip_header* iphdr = reinterpret_cast(pckt->buffer() - + sizeof(Ethernet::header)); - IP4::addr sip = iphdr->saddr; - IP4::addr dip = pckt->next_hop(); - - debug2(" physical> Transmitting %i bytes to %s\n", - pckt->size(), dip.str().c_str()); - - Ethernet::addr dest_mac; - - if (iphdr->daddr == IP4::INADDR_BCAST) { - // When broadcasting our source IP should be either - // our own IP or 0.0.0.0 - - if (sip != inet_.ip_addr() && sip != IP4::INADDR_ANY) { - debug2(" Dropping outbound broadcast packet due to " - "invalid source IP %s\n", sip.str().c_str()); - return; - } - // mui importante - dest_mac = Ethernet::addr::BROADCAST_FRAME; - - } else { - if (sip != inet_.ip_addr()) { - debug2(" physical> Not bound to source IP %s. My IP is %s. DROP!\n", - sip.str().c_str(), inet_.ip_addr().str().c_str()); - return; - } - - // If we don't have a cached IP, perform address resolution - if (!is_valid_cached(dip)) { - arp_resolver_(pckt); - return; - } - - // Get MAC from cache - dest_mac = cache_[dip].mac_; - } - - /** Attach next-hop mac and ethertype to ethernet header */ - Ethernet::header* ethhdr = reinterpret_cast(pckt->buffer()); - ethhdr->src = mac_; - ethhdr->dest = dest_mac; - ethhdr->type = Ethernet::ETH_IP4; - - debug2(" physical> Sending packet to %s\n", mac_.str().c_str()); - linklayer_out_(pckt); -} - -void Arp::await_resolution(Packet_ptr pckt, IP4::addr) { - auto queue = waiting_packets_.find(pckt->next_hop()); - - if (queue != waiting_packets_.end()) { - debug(" Packets already queueing for this IP\n"); - queue->second->chain(pckt); - } else { - debug(" This is the first packet going to that IP\n"); - waiting_packets_.emplace(std::make_pair(pckt->next_hop(), pckt)); - } -} - -void Arp::arp_resolve(Packet_ptr pckt) { - debug(" %s\n", pckt->next_hop().str().c_str()); - - await_resolution(pckt, pckt->next_hop()); - - auto req = view_packet_as(inet_.createPacket(sizeof(header))); - req->init(mac_, inet_.ip_addr()); - - req->set_dest_mac(Ethernet::addr::BROADCAST_FRAME); - req->set_dest_ip(pckt->next_hop()); - req->set_opcode(H_request); - - linklayer_out_(req); -} - -void Arp::hh_map(Packet_ptr pckt) { - (void) pckt; - debug("ARP-resolution using the HH-hack"); - /** - // Fixed mac prefix - mac.minor = 0x01c0; //Big-endian c001 - // Destination IP - mac.major = dip.whole; - debug("ARP cache missing. Guessing Mac %s from next-hop IP: %s (dest.ip: %s)", - mac.str().c_str(), dip.str().c_str(), iphdr->daddr.str().c_str()); - **/ -} - -} //< namespace net diff --git a/src/net/buffer_store.cpp b/src/net/buffer_store.cpp index 16000a4fef..d94afe3a64 100644 --- a/src/net/buffer_store.cpp +++ b/src/net/buffer_store.cpp @@ -24,80 +24,80 @@ namespace net { -BufferStore::BufferStore(size_t num, size_t bufsize, size_t device_offset ) : - bufcount_ {num}, - bufsize_ {bufsize}, - device_offset_ {device_offset}, - pool_ {static_cast(memalign(PAGE_SIZE, num * bufsize))} + BufferStore::BufferStore(size_t num, size_t bufsize, size_t device_offset ) : + bufcount_ {num}, + bufsize_ {bufsize}, + device_offset_ {device_offset}, + pool_ {static_cast(memalign(PAGE_SIZE, num * bufsize))} { assert(pool_); debug (" Creating buffer store of %i * %i bytes.\n", - num, bufsize); + num, bufsize); for (buffer_t b = pool_; b < pool_ + (num * bufsize); b += bufsize) available_buffers_.push_back(b); debug (" I now have %i free buffers in range %p -> %p.\n", - available_buffers_.size(), pool_, pool_ + (bufcount_ * bufsize_)); + available_buffers_.size(), pool_, pool_ + (bufcount_ * bufsize_)); } -BufferStore::~BufferStore() { - free(pool_); -} + BufferStore::~BufferStore() { + free(pool_); + } -/** - * @todo : We (think we) want a list of pools, that we increase as needed. - */ -void BufferStore::increaseStorage() { - panic(" Storage pool full! Don't know how to increase pool size yet.\n"); -} + /** + * @todo : We (think we) want a list of pools, that we increase as needed. + */ + void BufferStore::increaseStorage() { + panic(" Storage pool full! Don't know how to increase pool size yet.\n"); + } -BufferStore::buffer_t BufferStore::get_raw_buffer() { - if (available_buffers_.empty()) - increaseStorage(); + BufferStore::buffer_t BufferStore::get_raw_buffer() { + if (available_buffers_.empty()) + increaseStorage(); - auto buf = available_buffers_.front(); - available_buffers_.pop_front(); + auto buf = available_buffers_.front(); + available_buffers_.pop_front(); - debug2(" Provisioned a buffer. %i buffers remaining.\n", - available_buffers_.size()); - - return buf; -} - -BufferStore::buffer_t BufferStore::get_offset_buffer() { - return get_raw_buffer() + device_offset_; -} - -void BufferStore::release_raw_buffer(buffer_t b, size_t bufsize) { - debug2(" Trying to release %i sized buffer @%p.\n", bufsize, b); - // Make sure the buffer comes from here. Otherwise, ignore it. - if (address_is_from_pool(b) - and address_is_bufstart(b) - and bufsize == bufsize_) - { - available_buffers_.push_back(b); - debug(" Releasing %p. %i available buffers.\n", b, available_buffers_.size()); - return; - } - - debug(" IGNORING buffer @%p. It isn't mine.\n", b); -} - -void BufferStore::release_offset_buffer(buffer_t b, size_t bufsize) { - debug2(" Trying to release %i + %i sized buffer @%p.\n", bufsize, device_offset_, b); - // Make sure the buffer comes from here. Otherwise, ignore it. - if (address_is_from_pool(b) - and address_is_offset_bufstart(b) - and bufsize == bufsize_ - device_offset_) - { - available_buffers_.push_back(b - device_offset_); - debug(" Releasing %p. %i available buffers.\n", b, available_buffers_.size()); - return; - } - - debug(" IGNORING buffer @%p. It isn't mine.\n", b); -} + debug2(" Provisioned a buffer. %i buffers remaining.\n", + available_buffers_.size()); + + return buf; + } + + BufferStore::buffer_t BufferStore::get_offset_buffer() { + return get_raw_buffer() + device_offset_; + } + + void BufferStore::release_raw_buffer(buffer_t b, size_t bufsize) { + debug2(" Trying to release %i sized buffer @%p.\n", bufsize, b); + // Make sure the buffer comes from here. Otherwise, ignore it. + if (address_is_from_pool(b) + and address_is_bufstart(b) + and bufsize == bufsize_) + { + available_buffers_.push_back(b); + debug(" Releasing %p. %i available buffers.\n", b, available_buffers_.size()); + return; + } + + debug(" IGNORING buffer @%p. It isn't mine.\n", b); + } + + void BufferStore::release_offset_buffer(buffer_t b, size_t bufsize) { + debug2(" Trying to release %i + %i sized buffer @%p.\n", bufsize, device_offset_, b); + // Make sure the buffer comes from here. Otherwise, ignore it. + if (address_is_from_pool(b) + and address_is_offset_bufstart(b) + and bufsize == bufsize_ - device_offset_) + { + available_buffers_.push_back(b - device_offset_); + debug(" Releasing %p. %i available buffers.\n", b, available_buffers_.size()); + return; + } + + debug(" IGNORING buffer @%p. It isn't mine.\n", b); + } } //< namespace net diff --git a/src/net/dhcp/dh4client.cpp b/src/net/dhcp/dh4client.cpp index 9e2f8740f0..d430f6e000 100644 --- a/src/net/dhcp/dh4client.cpp +++ b/src/net/dhcp/dh4client.cpp @@ -6,9 +6,9 @@ // 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. @@ -27,20 +27,20 @@ // BOOTP (rfc951) message types #define BOOTREQUEST 1 #define BOOTREPLY 2 - + // Possible values for flags field #define BOOTP_UNICAST 0x0000 #define BOOTP_BROADCAST 0x8000 - + // Possible values for hardware type (htype) field #define HTYPE_ETHER 1 // Ethernet 10Mbps #define HTYPE_IEEE802 6 // IEEE 802.2 Token Ring #define HTYPE_FDDI 8 // FDDI - + /* Magic cookie validating dhcp options field (and bootp vendor extensions field). */ #define DHCP_OPTIONS_COOKIE "\143\202\123\143" - + // DHCP Option codes #define DHO_PAD 0 #define DHO_SUBNET_MASK 1 @@ -155,16 +155,41 @@ namespace net return (dhcp_option_t*) option; } - void DHClient::negotiate() + DHClient::DHClient(Stack& inet) + : stack(inet), xid(0), console_spam(true) + { + config_handler = + [this] (bool timeout) { + if (console_spam) + { + if (timeout) + INFO("DHCPv4","Negotiation timed out"); + else + INFO("DHCPv4","Config complete"); + } + }; + } + + void DHClient::negotiate(double timeout_secs) { + // set timeout handler + this->timeout = hw::PIT::instance().on_timeout(timeout_secs, + [this] { + // reset session ID + this->xid = 0; + // call on_config with timeout = true + this->config_handler(true); + }); + // create a random session ID this->xid = OS::cycles_since_boot() & 0xFFFFFFFF; - MYINFO("Negotiating IP-address (xid=%u)", xid); - + if (console_spam) + MYINFO("Negotiating IP-address (xid=%u)", xid); + // create DHCP discover packet const size_t packetlen = sizeof(dhcp_packet_t); char packet[packetlen]; - + dhcp_packet_t* dhcp = (dhcp_packet_t*) packet; dhcp->op = BOOTREQUEST; dhcp->htype = HTYPE_ETHER; @@ -177,20 +202,20 @@ namespace net dhcp->yiaddr = IP4::INADDR_ANY; dhcp->siaddr = IP4::INADDR_ANY; dhcp->giaddr = IP4::INADDR_ANY; - + Ethernet::addr link_addr = stack.link_addr(); - + // copy our hardware address to chaddr field memset(dhcp->chaddr, 0, dhcp_packet_t::CHADDR_LEN); memcpy(dhcp->chaddr, &link_addr, ETH_ALEN); // zero server, file and options memset(dhcp->sname, 0, dhcp_packet_t::SNAME_LEN + dhcp_packet_t::FILE_LEN); - + dhcp->magic[0] = 99; dhcp->magic[1] = 130; dhcp->magic[2] = 83; dhcp->magic[3] = 99; - + dhcp_option_t* opt = conv_option(dhcp->options + 0); // DHCP discover opt->code = DHO_DHCP_MESSAGE_TYPE; @@ -213,82 +238,80 @@ namespace net opt = conv_option(dhcp->options + 17); opt->code = DHO_END; opt->length = 0; - + //////////////////////////////////////////////////////// auto& socket = stack.udp().bind(DHCP_SOURCE_PORT); /// broadcast our DHCP plea as 0.0.0.0:67 socket.bcast(IP4::INADDR_ANY, DHCP_DEST_PORT, packet, packetlen); - - socket.onRead( - [this] (Socket& sock, IP4::addr addr, UDP::port_t port, - const char* data, int len) -> int + + socket.on_read( + [this, &socket] (IP4::addr, UDP::port_t port, + const char* data, size_t len) { - (void) addr; if (port == DHCP_DEST_PORT) { // we have got a DHCP Offer debug("Received possible DHCP OFFER from %s:%d\n", - addr.str().c_str(), DHCP_DEST_PORT); - this->offer(sock, data, len); + addr.str().c_str(), DHCP_DEST_PORT); + this->offer(socket, data, len); } - return -1; }); } - + const dhcp_option_t* get_option(const uint8_t* options, uint8_t code) { const dhcp_option_t* opt = (const dhcp_option_t*) options; while (opt->code != code && opt->code != DHO_END) - { - // go to next option - opt = (const dhcp_option_t*) (((const uint8_t*) opt) + 2 + opt->length); - } + { + // go to next option + opt = (const dhcp_option_t*) (((const uint8_t*) opt) + 2 + opt->length); + } return opt; } - - void DHClient::offer(Socket& sock, const char* data, int datalen) + + void DHClient::offer(UDPSocket& sock, const char* data, size_t) { - (void) datalen; const dhcp_packet_t* dhcp = (const dhcp_packet_t*) data; - + uint32_t xid = htonl(dhcp->xid); // silently ignore transactions not our own if (xid != this->xid) return; - + // check if the BOOTP message is a DHCP OFFER const dhcp_option_t* opt; opt = get_option(dhcp->options, DHO_DHCP_MESSAGE_TYPE); - + if (opt->code == DHO_DHCP_MESSAGE_TYPE) { // verify that the type is indeed DHCPOFFER debug("Found DHCP message type %d (DHCP Offer = %d)\n", - opt->val[0], DHCPOFFER); + opt->val[0], DHCPOFFER); // ignore when not a DHCP Offer if (opt->val[0] != DHCPOFFER) return; } // ignore message when DHCP message type is missing else return; - + // the offered IP address: this->ipaddr = dhcp->yiaddr; - MYINFO("IP ADDRESS: \t%s", - this->ipaddr.str().c_str()); + if (console_spam) + MYINFO("IP ADDRESS: \t%s", this->ipaddr.str().c_str()); opt = get_option(dhcp->options, DHO_SUBNET_MASK); if (opt->code == DHO_SUBNET_MASK) { memcpy(&this->netmask, opt->val, sizeof(IP4::addr)); - MYINFO("SUBNET MASK: \t%s", - this->netmask.str().c_str()); + if (console_spam) + MYINFO("SUBNET MASK: \t%s", this->netmask.str().c_str()); } opt = get_option(dhcp->options, DHO_DHCP_LEASE_TIME); if (opt->code == DHO_DHCP_LEASE_TIME) { memcpy(&this->lease_time, opt->val, sizeof(this->lease_time)); - MYINFO("LEASE TIME: \t%u mins", this->lease_time / 60); + if (console_spam) + MYINFO("LEASE TIME: \t%u mins", this->lease_time / 60); } // now validate the offer, checking for minimum information @@ -296,8 +319,8 @@ namespace net if (opt->code == DHO_ROUTERS) { memcpy(&this->router, opt->val, sizeof(IP4::addr)); - MYINFO("GATEWAY: \t%s", - this->router.str().c_str()); + if (console_spam) + MYINFO("GATEWAY: \t%s", this->router.str().c_str()); } // assume that the server we received the request from is the gateway else @@ -306,8 +329,8 @@ namespace net if (opt->code == DHO_DHCP_SERVER_IDENTIFIER) { memcpy(&this->router, opt->val, sizeof(IP4::addr)); - MYINFO("GATEWAY: \t%s", - this->router.str().c_str()); + if (console_spam) + MYINFO("GATEWAY: \t%s", this->router.str().c_str()); } // silently ignore when both ROUTER and SERVER_ID is missing else return; @@ -322,18 +345,19 @@ namespace net { // just try using ROUTER as DNS server this->dns_server = this->router; } - MYINFO("DNS SERVER: \t%s", this->dns_server.str().c_str()); + if (console_spam) + MYINFO("DNS SERVER: \t%s", this->dns_server.str().c_str()); // we can accept the offer now by requesting the IP! this->request(sock); } - - void DHClient::request(Socket& sock) + + void DHClient::request(UDPSocket& sock) { // form a response const size_t packetlen = sizeof(dhcp_packet_t); char packet[packetlen]; - + dhcp_packet_t* resp = (dhcp_packet_t*) packet; resp->op = BOOTREQUEST; resp->htype = HTYPE_ETHER; @@ -342,14 +366,14 @@ namespace net resp->xid = htonl(this->xid); resp->secs = 0; resp->flags = htons(BOOTP_UNICAST); - + resp->ciaddr = IP4::INADDR_ANY; resp->yiaddr = IP4::INADDR_ANY; resp->siaddr = IP4::INADDR_ANY; resp->giaddr = IP4::INADDR_ANY; - + Ethernet::addr link_addr = stack.link_addr(); - + // copy our hardware address to chaddr field memset(resp->chaddr, 0, dhcp_packet_t::CHADDR_LEN); memcpy(resp->chaddr, &link_addr, ETH_ALEN); @@ -360,7 +384,7 @@ namespace net resp->magic[1] = 130; resp->magic[2] = 83; resp->magic[3] = 99; - + dhcp_option_t* opt = conv_option(resp->options + 0); // DHCP Request opt->code = DHO_DHCP_MESSAGE_TYPE; @@ -393,57 +417,57 @@ namespace net opt = conv_option(resp->options + 29); opt->code = DHO_END; opt->length = 0; - + // set our onRead function to point to a hopeful DHCP ACK! - sock.onRead( - [this] (Socket&, IP4::addr addr, UDP::port_t port, - const char* data, int len) -> int + sock.on_read( + [this] (IP4::addr, UDP::port_t port, + const char* data, size_t len) { - (void) addr; if (port == DHCP_DEST_PORT) { // we have hopefully got a DHCP Ack debug("\tReceived DHCP ACK from %s:%d\n", - addr.str().c_str(), DHCP_DEST_PORT); + addr.str().c_str(), DHCP_DEST_PORT); this->acknowledge(data, len); } - return -1; }); // send our DHCP Request sock.bcast(IP4::INADDR_ANY, DHCP_DEST_PORT, packet, packetlen); } - - void DHClient::acknowledge(const char* data, int datalen) + + void DHClient::acknowledge(const char* data, size_t) { - (void) datalen; const dhcp_packet_t* dhcp = (const dhcp_packet_t*) data; - + uint32_t xid = htonl(dhcp->xid); // silently ignore transactions not our own if (xid != this->xid) return; - + // check if the BOOTP message is a DHCP OFFER const dhcp_option_t* opt; opt = get_option(dhcp->options, DHO_DHCP_MESSAGE_TYPE); - + if (opt->code == DHO_DHCP_MESSAGE_TYPE) { // verify that the type is indeed DHCPOFFER debug("\tFound DHCP message type %d (DHCP Ack = %d)\n", - opt->val[0], DHCPACK); - + opt->val[0], DHCPACK); // ignore when not a DHCP Offer if (opt->val[0] != DHCPACK) return; } // ignore message when DHCP message type is missing else return; + + if (console_spam) + MYINFO("Server acknowledged our request!"); // configure our network stack - MYINFO("Server acknowledged our request!"); - stack.network_config(this->ipaddr, this->netmask, + stack.network_config(this->ipaddr, this->netmask, this->router, this->dns_server); + // stop timeout from happening + hw::PIT::stop(timeout); // run some post-DHCP event to release the hounds - this->config_handler(stack); + this->config_handler(false); } } diff --git a/src/net/dns/client.cpp b/src/net/dns/client.cpp index 3b8fffa4fb..41e5584bb5 100644 --- a/src/net/dns/client.cpp +++ b/src/net/dns/client.cpp @@ -6,9 +6,9 @@ // 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. @@ -24,29 +24,28 @@ namespace net { void DNSClient::resolve(IP4::addr dns_server, const std::string& hostname, Stack::resolve_func func) { - UDP::port_t port = 33314; // <-- FIXME: should be automatic port - auto& sock = stack.udp().bind(port); - + auto& sock = stack.udp().bind(); + // create DNS request DNS::Request request; - char* data = new char[256]; - int len = request.create(data, hostname); - + auto* data = new char[256]; + size_t len = request.create(data, hostname); + // send request to DNS server sock.sendto(dns_server, DNS::DNS_SERVICE_PORT, data, len); delete[] data; - + // wait for response // FIXME: WE DO NOT CHECK TRANSACTION IDS HERE (yet), GOD HELP US ALL - sock.onRead( [this, hostname, request, func] - (Socket&, IP4::addr, UDP::port_t, const char* data, int) mutable -> int + sock.on_read( + [this, hostname, request, func] + (IP4::addr, UDP::port_t, const char* data, size_t) mutable { // original request ID = this->id; request.parseResponse(data); - // fire onResolve event - func(this->stack, hostname, request.getFirstIP4()); - return -1; + // fire onResolve event + func(request.getFirstIP4()); }); } } diff --git a/src/net/dns/dns.cpp b/src/net/dns/dns.cpp index a1e6a39537..a671c60ad4 100644 --- a/src/net/dns/dns.cpp +++ b/src/net/dns/dns.cpp @@ -32,12 +32,12 @@ namespace net std::string resp; while (*(tmp)!=0) - { - int len = *tmp++; - resp.append((char*) tmp, len); - resp.append("."); - tmp += len; - } + { + int len = *tmp++; + resp.append((char*) tmp, len); + resp.append("."); + tmp += len; + } return resp; } @@ -79,7 +79,7 @@ namespace net // initial response size unsigned short packetlen = sizeof(header) + - sizeof(question) + parsed_query.size() + 1; + sizeof(question) + parsed_query.size() + 1; // set DNS QR to RESPONSE hdr.qr = DNS_QR_RESPONSE; @@ -91,45 +91,45 @@ namespace net std::vector* addrs = lookup(parsed_query); if (addrs == nullptr) - { - // not found - debug("*** Could not find: %s", parsed_query.c_str()); - hdr.ans_count = 0; - hdr.rcode = DNS::NO_ERROR; - } + { + // not found + debug("*** Could not find: %s", parsed_query.c_str()); + hdr.ans_count = 0; + hdr.rcode = DNS::NO_ERROR; + } else - { - debug("*** Found %lu results for %s", addrs->size(), parsed_query.c_str()); - // append answers - for (auto addr : *addrs) { - debug("*** Result: %s", addr.str().c_str()); - // add query - int qlen = parsed_query.size() + 1; - memcpy(buffer, query, qlen); - buffer += qlen; - packetlen += qlen; // (!) + debug("*** Found %lu results for %s", addrs->size(), parsed_query.c_str()); + // append answers + for (auto addr : *addrs) + { + debug("*** Result: %s", addr.str().c_str()); + // add query + int qlen = parsed_query.size() + 1; + memcpy(buffer, query, qlen); + buffer += qlen; + packetlen += qlen; // (!) - // add resource record - rr_data* data = (rr_data*) buffer; + // add resource record + rr_data* data = (rr_data*) buffer; - data->type = htons(DNS_TYPE_A); - data->_class = htons(DNS_CLASS_INET); - data->ttl = htons(0x7FFF); // just because - data->data_len = htons(sizeof(IP4::addr)); - buffer += sizeof(rr_data); + data->type = htons(DNS_TYPE_A); + data->_class = htons(DNS_CLASS_INET); + data->ttl = htons(0x7FFF); // just because + data->data_len = htons(sizeof(IP4::addr)); + buffer += sizeof(rr_data); - // add resource itself - *((IP4::addr*) buffer) = addr; // IPv4 address - buffer += sizeof(IP4::addr); + // add resource itself + *((IP4::addr*) buffer) = addr; // IPv4 address + buffer += sizeof(IP4::addr); - packetlen += sizeof(rr_data) + sizeof(IP4::addr); // (!) - } // addr + packetlen += sizeof(rr_data) + sizeof(IP4::addr); // (!) + } // addr - // set dns header answer count (!) - hdr.ans_count = htons((addrs->size() & 0xFFFF)); - hdr.rcode = DNS::NO_ERROR; - } + // set dns header answer count (!) + hdr.ans_count = htons((addrs->size() & 0xFFFF)); + hdr.rcode = DNS::NO_ERROR; + } return packetlen; } @@ -160,7 +160,7 @@ namespace net dns->auth_count = 0; dns->add_count = 0; - // point to the query portion + // point to the query portion char* qname = buffer + sizeof(DNS::header); // convert host to dns name format @@ -197,7 +197,7 @@ namespace net for (int i = 0; i < ntohs(dns->auth_count); i++) auth.emplace_back(reader, buffer); - // parse additional + // parse additional for (int i = 0; i < ntohs(dns->add_count); i++) addit.emplace_back(reader, buffer); @@ -231,24 +231,24 @@ namespace net // convert www.google.com to 3www6google3com void DNS::Request::dnsNameFormat(char* dns) { - int lock = 0; + int lock = 0; - std::string copy = this->hostname + "."; - int len = copy.size(); + std::string copy = this->hostname + "."; + int len = copy.size(); - for(int i = 0; i < len; i++) + for(int i = 0; i < len; i++) { - if (copy[i] == '.') + if (copy[i] == '.') { - *dns++ = i - lock; - for(; lock < i; lock++) + *dns++ = i - lock; + for(; lock < i; lock++) { - *dns++ = copy[lock]; + *dns++ = copy[lock]; } - lock++; + lock++; } } - *dns++ = '\0'; + *dns++ = '\0'; } DNS::Request::rr_t::rr_t(const char*& reader, const char* buffer) @@ -263,54 +263,50 @@ namespace net // if its an ipv4 address if (ntohs(resource.type) == DNS_TYPE_A) - { - int len = ntohs(resource.data_len); + { + int len = ntohs(resource.data_len); - this->rdata = std::string(reader, len); - reader += len; - } + this->rdata = std::string(reader, len); + reader += len; + } else - { - this->rdata = readName(reader, buffer, stop); - reader += stop; - } + { + this->rdata = readName(reader, buffer, stop); + reader += stop; + } } IP4::addr DNS::Request::rr_t::getIP4() const { - switch (ntohs(resource.type)) - { + switch (ntohs(resource.type)) { case DNS_TYPE_A: - { - IP4::addr* addr = (IP4::addr*) rdata.c_str(); - return *addr; - } + return *(IP4::addr*) rdata.data(); case DNS_TYPE_ALIAS: case DNS_TYPE_NS: default: - return IP4::addr{{0}}; + return IP4::INADDR_ANY; } } void DNS::Request::rr_t::print() { printf("Name: %s ", name.c_str()); switch (ntohs(resource.type)) - { - case DNS_TYPE_A: { - IP4::addr* addr = (IP4::addr*) rdata.c_str(); - printf("has IPv4 address: %s", addr->str().c_str()); + case DNS_TYPE_A: + { + auto* addr = (IP4::addr*) rdata.data(); + printf("has IPv4 address: %s", addr->str().c_str()); + } + break; + case DNS_TYPE_ALIAS: + printf("has alias: %s", rdata.c_str()); + break; + case DNS_TYPE_NS: + printf("has authoritative nameserver : %s", rdata.c_str()); + break; + default: + printf("has unknown resource type: %d", ntohs(resource.type)); } - break; - case DNS_TYPE_ALIAS: - printf("has alias: %s", rdata.c_str()); - break; - case DNS_TYPE_NS: - printf("has authoritative nameserver : %s", rdata.c_str()); - break; - default: - printf("has unknown resource type: %d", ntohs(resource.type)); - } printf("\n"); } @@ -325,22 +321,22 @@ namespace net unsigned char* ureader = (unsigned char*) reader; while (*ureader) - { - if (*ureader >= 192) { - offset = (*ureader) * 256 + *(ureader+1) - 49152; // = 11000000 00000000 - ureader = (unsigned char*) buffer + offset - 1; - jumped = true; // we have jumped to another location so counting wont go up! - } - else - { - name[p++] = *ureader; - } - ureader++; + if (*ureader >= 192) + { + offset = (*ureader) * 256 + *(ureader+1) - 49152; // = 11000000 00000000 + ureader = (unsigned char*) buffer + offset - 1; + jumped = true; // we have jumped to another location so counting wont go up! + } + else + { + name[p++] = *ureader; + } + ureader++; - // if we havent jumped to another location then we can count up - if (jumped == false) count++; - } + // if we havent jumped to another location then we can count up + if (jumped == false) count++; + } name.resize(p); // number of steps we actually moved forward in the packet @@ -351,16 +347,16 @@ namespace net int len = p; // same as name.size() int i; for(i = 0; i < len; i++) - { - p = name[i]; - - for(unsigned j = 0; j < p; j++) { - name[i] = name[i+1]; - i++; + p = name[i]; + + for(unsigned j = 0; j < p; j++) + { + name[i] = name[i+1]; + i++; + } + name[i] = '.'; } - name[i] = '.'; - } name[i - 1] = '\0'; // remove the last dot return name; diff --git a/src/net/ethernet.cpp b/src/net/ethernet.cpp index 1a690fe46f..1036ce9379 100644 --- a/src/net/ethernet.cpp +++ b/src/net/ethernet.cpp @@ -6,110 +6,112 @@ // 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 DEBUG // Allow debugging -#define DEBUG2 +//#define DEBUG // Allow debugging +//#define DEBUG2 #include + +#include + #include #include #include -#include namespace net { -// uint16_t(0x0000), uint32_t(0x01000000) -const Ethernet::addr Ethernet::addr::MULTICAST_FRAME {{0,0,0x01,0,0,0}}; + // uint16_t(0x0000), uint32_t(0x01000000) + const Ethernet::addr Ethernet::addr::MULTICAST_FRAME {{0,0,0x01,0,0,0}}; -// uint16_t(0xFFFF), uint32_t(0xFFFFFFFF) -const Ethernet::addr Ethernet::addr::BROADCAST_FRAME {{0xff,0xff,0xff,0xff,0xff,0xff}}; + // uint16_t(0xFFFF), uint32_t(0xFFFFFFFF) + const Ethernet::addr Ethernet::addr::BROADCAST_FRAME {{0xff,0xff,0xff,0xff,0xff,0xff}}; -// uint16_t(0x3333), uint32_t(0x01000000) -const Ethernet::addr Ethernet::addr::IPv6mcast_01 {{0x33,0x33,0x01,0,0,0}}; + // uint16_t(0x3333), uint32_t(0x01000000) + const Ethernet::addr Ethernet::addr::IPv6mcast_01 {{0x33,0x33,0x01,0,0,0}}; -// uint16_t(0x3333), uint32_t(0x02000000) -const Ethernet::addr Ethernet::addr::IPv6mcast_02 {{0x33,0x33,0x02,0,0,0}}; + // uint16_t(0x3333), uint32_t(0x02000000) + const Ethernet::addr Ethernet::addr::IPv6mcast_02 {{0x33,0x33,0x02,0,0,0}}; -static void ignore(Packet_ptr UNUSED(pckt)) noexcept { - debug(" Ignoring data (no real handler)\n"); -} + static void ignore(Packet_ptr UNUSED(pckt)) noexcept { + debug(" Ignoring data (no real handler)\n"); + } -Ethernet::Ethernet(addr mac) noexcept + Ethernet::Ethernet(addr mac) noexcept : mac_(mac), ip4_handler_{ignore}, ip6_handler_{ignore}, arp_handler_{ignore} {} -void Ethernet::transmit(Packet_ptr pckt) { - header* hdr = reinterpret_cast(pckt->buffer()); - - // Verify ethernet header - assert(hdr->dest.major != 0 || hdr->dest.minor !=0); - assert(hdr->type != 0); - - // Add source address - hdr->src = mac_; - - debug2(" Transmitting %i b, from %s -> %s. Type: %i\n", - pckt->size(), mac_.str().c_str(), hdr->dest.str().c_str(), hdr->type); - - physical_out_(pckt); -} - -void Ethernet::bottom(Packet_ptr pckt) { - assert(pckt->size() > 0); - - header* eth = reinterpret_cast(pckt->buffer()); - - /** Do we pass on ethernet headers? As for now, yes. - data += sizeof(header); - len -= sizeof(header); - */ - debug2(" %s => %s , Eth.type: 0x%x ", - eth->src.str().c_str(), eth->dest.str().c_str(), eth->type); - - switch(eth->type) { - case ETH_IP4: - debug2("IPv4 packet\n"); - ip4_handler_(pckt); - break; - - case ETH_IP6: - debug2("IPv6 packet\n"); - ip6_handler_(pckt); - break; - - case ETH_ARP: - debug2("ARP packet\n"); - arp_handler_(pckt); - break; - - case ETH_WOL: - debug2("Wake-on-LAN packet\n"); - break; - - case ETH_VLAN: - debug("VLAN tagged frame (not yet supported)"); - break; - - default: - // This might be 802.3 LLC traffic - if (net::ntohs(eth->type) > 1500) { - debug(" UNKNOWN ethertype 0x%x\n", ntohs(eth->type)); - }else { - debug2("IEEE802.3 Length field: 0x%x\n", ntohs(eth->type)); + void Ethernet::transmit(Packet_ptr pckt) { + header* hdr = reinterpret_cast(pckt->buffer()); + + // Verify ethernet header + Expects(hdr->dest.major != 0 || hdr->dest.minor !=0); + Expects(hdr->type != 0); + + // Add source address + hdr->src = mac_; + + debug2(" Transmitting %i b, from %s -> %s. Type: %i\n", + pckt->size(), mac_.str().c_str(), hdr->dest.str().c_str(), hdr->type); + + physical_out_(pckt); + } + + void Ethernet::bottom(Packet_ptr pckt) { + Expects(pckt->size() > 0); + + header* eth = reinterpret_cast(pckt->buffer()); + + /** Do we pass on ethernet headers? As for now, yes. + data += sizeof(header); + len -= sizeof(header); + */ + debug2(" %s => %s , Eth.type: 0x%x ", + eth->src.str().c_str(), eth->dest.str().c_str(), eth->type); + + switch(eth->type) { + case ETH_IP4: + debug2("IPv4 packet\n"); + ip4_handler_(pckt); + break; + + case ETH_IP6: + debug2("IPv6 packet\n"); + ip6_handler_(pckt); + break; + + case ETH_ARP: + debug2("ARP packet\n"); + arp_handler_(pckt); + break; + + case ETH_WOL: + debug2("Wake-on-LAN packet\n"); + break; + + case ETH_VLAN: + debug("VLAN tagged frame (not yet supported)"); + break; + + default: + // This might be 802.3 LLC traffic + if (net::ntohs(eth->type) > 1500) { + debug(" UNKNOWN ethertype 0x%x\n", ntohs(eth->type)); + }else { + debug2("IEEE802.3 Length field: 0x%x\n", ntohs(eth->type)); + } + break; } - break; } -} } // namespace net diff --git a/src/net/inet.cpp b/src/net/inet.cpp index a22428e9e3..760c9a2931 100644 --- a/src/net/inet.cpp +++ b/src/net/inet.cpp @@ -22,10 +22,10 @@ namespace net { Inet::Inet() : - //_eth(eth0.mac()),_arp(eth0.mac(),ip) - _ip4(_ip4_list[0],_netmask_list[0]), _udp(_ip4_list[0]), - _ip6(_ip6_list[0]), - _icmp6(_ip6_list[0]), _udp6(_ip6_list[0]) + //_eth(eth0.mac()),_arp(eth0.mac(),ip) + _ip4(_ip4_list[0],_netmask_list[0]), _udp(_ip4_list[0]), + _ip6(_ip6_list[0]), + _icmp6(_ip6_list[0]), _udp6(_ip6_list[0]) { // For now we're just using the one interface auto& eth0 = Dev::eth<0,VirtioNet>(); diff --git a/src/net/inet_common.cpp b/src/net/inet_common.cpp index e5b135f7d5..e96efc4a44 100644 --- a/src/net/inet_common.cpp +++ b/src/net/inet_common.cpp @@ -23,27 +23,27 @@ namespace net { -// Should be pretty much like the example in RFC 1071, -// but using a uinon for readability -uint16_t checksum(void* data, size_t len) noexcept { + // Should be pretty much like the example in RFC 1071, + // but using a uinon for readability + uint16_t checksum(void* data, size_t len) noexcept { - uint16_t* buf = reinterpret_cast(data); + uint16_t* buf = reinterpret_cast(data); - union sum { - uint32_t whole; - uint16_t part[2]; - } sum32 {0}; + union sum { + uint32_t whole; + uint16_t part[2]; + } sum32 {0}; - // Iterate in short int steps. - for (uint16_t* i = buf; i < (buf + len / 2); ++i) - sum32.whole += *i; + // Iterate in short int steps. + for (uint16_t* i = buf; i < (buf + len / 2); ++i) + sum32.whole += *i; - // odd-length case - if (len & 1) { - sum32.whole += reinterpret_cast(buf)[len - 1]; - } + // odd-length case + if (len & 1) { + sum32.whole += reinterpret_cast(buf)[len - 1]; + } - return ~(sum32.part[0] + sum32.part[1]); -} + return ~(sum32.part[0] + sum32.part[1]); + } } //< namespace net diff --git a/src/net/ip4.cpp b/src/net/ip4.cpp deleted file mode 100644 index 537e8da023..0000000000 --- a/src/net/ip4.cpp +++ /dev/null @@ -1,113 +0,0 @@ -// This file is a part of the IncludeOS unikernel - www.includeos.org -// -// Copyright 2015 Oslo and Akershus University College of Applied Sciences -// and Alfred Bratterud -// -// 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 DEBUG // Allow debugging -#define DEBUG2 // Allow debug lvl 2 -#include -#include -#include -#include - -namespace net { - -const IP4::addr IP4::INADDR_ANY {{0,0,0,0}}; -const IP4::addr IP4::INADDR_BCAST {{0xff,0xff,0xff,0xff}}; - -IP4::IP4(Inet& inet) noexcept: - stack_{inet} -{ - // Default gateway is addr 1 in the subnet. - // const uint32_t DEFAULT_GATEWAY = htonl(1); - // gateway_.whole = (local_ip_.whole & netmask_.whole) | DEFAULT_GATEWAY; -} - -void IP4::bottom(Packet_ptr pckt) { - debug2(" got the data.\n"); - - auto data = pckt->buffer(); - ip_header* hdr = &reinterpret_cast(data)->ip_hdr; - - debug2("\t Source IP: %s Dest.IP: %s\n", - hdr->saddr.str().c_str(), hdr->daddr.str().c_str()); - - switch(hdr->protocol){ - case IP4_ICMP: - debug2("\t Type: ICMP\n"); - icmp_handler_(pckt); - break; - case IP4_UDP: - debug2("\t Type: UDP\n"); - udp_handler_(pckt); - break; - case IP4_TCP: - tcp_handler_(pckt); - debug2("\t Type: TCP\n"); - break; - default: - debug("\t Type: UNKNOWN %i\n", hdr->protocol); - break; - } -} - -uint16_t IP4::checksum(ip_header* hdr) { - return net::checksum(reinterpret_cast(hdr), sizeof(ip_header)); -} - -void IP4::transmit(Packet_ptr pckt) { - assert(pckt->size() > sizeof(IP4::full_header)); - - full_header* full_hdr = reinterpret_cast(pckt->buffer()); - ip_header* hdr = &full_hdr->ip_hdr; - - auto ip4_pckt = std::static_pointer_cast(pckt); - ip4_pckt->make_flight_ready(); - - // Create local and target subnets - addr target, local; - target.whole = hdr->daddr.whole & stack_.netmask().whole; - local.whole = stack_.ip_addr().whole & stack_.netmask().whole; - - // Compare subnets to know where to send packet - pckt->next_hop(target == local ? hdr->daddr : stack_.router()); - - debug(" Next hop for %s, (netmask %s, local IP: %s, gateway: %s) == %s\n", - hdr->daddr.str().c_str(), - stack_.netmask().str().c_str(), - stack_.ip_addr().str().c_str(), - stack_.router().str().c_str(), - target == local ? "DIRECT" : "GATEWAY"); - - debug(" my ip: %s, Next hop: %s, Packet size: %i IP4-size: %i\n", - stack_.ip_addr().str().c_str(), - pckt->next_hop().str().c_str(), - pckt->size(), - ip4_pckt->ip4_segment_size() - ); - - linklayer_out_(pckt); -} - -// Empty handler for delegates initialization -void ignore_ip4_up(Packet_ptr UNUSED(pckt)) { - debug(" Empty handler. Ignoring.\n"); -} - -void ignore_ip4_down(Packet_ptr UNUSED(pckt)) { - debug("Link layer> No handler - DROP!\n"); -} - -} //< namespace net diff --git a/src/net/ip4/arp.cpp b/src/net/ip4/arp.cpp new file mode 100644 index 0000000000..a502f16f6b --- /dev/null +++ b/src/net/ip4/arp.cpp @@ -0,0 +1,231 @@ +// This file is a part of the IncludeOS unikernel - www.includeos.org +// +// Copyright 2015 Oslo and Akershus University College of Applied Sciences +// and Alfred Bratterud +// +// 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 DEBUG // Allow debugging +#define DEBUG2 // Allow debugging + +#include + +#include +#include +#include +#include + +namespace net { + + static void ignore(Packet_ptr UNUSED(pckt)) { + debug2(" linklayer> Empty handler - DROP!\n"); + } + + // Initialize + Arp::Arp(net::Inet& inet) noexcept: + inet_ {inet}, + mac_ (inet.link_addr()), + linklayer_out_ {ignore} +{} + + void Arp::bottom(Packet_ptr pckt) { + debug2(" got %i bytes of data\n", pckt->size()); + + header* hdr = reinterpret_cast(pckt->buffer()); + + debug2("Have valid cache? %s\n", is_valid_cached(hdr->sipaddr) ? "YES" : "NO"); + cache(hdr->sipaddr, hdr->shwaddr); + + switch(hdr->opcode) { + + case H_request: { + debug2("\t ARP REQUEST: "); + debug2("%s is looking for %s\n", + hdr->sipaddr.str().c_str(), + hdr->dipaddr.str().c_str()); + + if (hdr->dipaddr == inet_.ip_addr()) { + arp_respond(hdr); + } else { + debug2("\t NO MATCH for My IP (%s). DROP!\n", + inet_.ip_addr().str().c_str()); + } + break; + } + + case H_reply: { + debug2("\t ARP REPLY: %s belongs to %s\n", + hdr->sipaddr.str().c_str(), hdr->shwaddr.str().c_str()); + + auto waiting = waiting_packets_.find(hdr->sipaddr); + + if (waiting != waiting_packets_.end()) { + debug("Had a packet waiting for this IP. Sending\n"); + transmit(waiting->second); + waiting_packets_.erase(waiting); + } + break; + } + + default: + debug2("\t UNKNOWN OPCODE\n"); + break; + } //< switch(hdr->opcode) + } + + void Arp::cache(IP4::addr ip, Ethernet::addr mac) { + debug2("Caching IP %s for %s\n", ip.str().c_str(), mac.str().c_str()); + + auto entry = cache_.find(ip); + + if (entry != cache_.end()) { + debug2("Cached entry found: %s recorded @ %llu. Updating timestamp\n", + entry->second.mac_.str().c_str(), entry->second.timestamp_); + + // Update + entry->second.update(); + + } else { + cache_[ip] = mac; // Insert + } + } + + bool Arp::is_valid_cached(IP4::addr ip) { + auto entry = cache_.find(ip); + + if (entry != cache_.end()) { + debug("Cached entry, mac: %s time: %llu Expiry: %llu\n", + entry->second.mac_.str().c_str(), + entry->second.timestamp_, entry->second.timestamp_ + cache_exp_t_); + debug("Time now: %llu\n", static_cast(OS::uptime())); + } + + return entry != cache_.end() + and (entry->second.timestamp_ + cache_exp_t_ > static_cast(OS::uptime())); + } + + extern "C" { + unsigned long ether_crc(int length, unsigned char *data); + } + + void Arp::arp_respond(header* hdr_in) { + debug2("\t IP Match. Constructing ARP Reply\n"); + + // Populate ARP-header + auto res = std::static_pointer_cast(inet_.createPacket(sizeof(header))); + res->init(mac_, inet_.ip_addr()); + + res->set_dest_mac(hdr_in->shwaddr); + res->set_dest_ip(hdr_in->sipaddr); + res->set_opcode(H_reply); + + debug2("\t My IP: %s belongs to My Mac: %s\n", + res->source_ip().str().c_str(), res->source_mac().str().c_str()); + + linklayer_out_(res); + } + + void Arp::transmit(Packet_ptr pckt) { + assert(pckt->size()); + + /** Get destination IP from IP header */ + IP4::ip_header* iphdr = reinterpret_cast(pckt->buffer() + + sizeof(Ethernet::header)); + IP4::addr sip = iphdr->saddr; + IP4::addr dip = pckt->next_hop(); + + debug2(" physical> Transmitting %i bytes to %s\n", + pckt->size(), dip.str().c_str()); + + Ethernet::addr dest_mac; + + if (iphdr->daddr == IP4::INADDR_BCAST) { + // When broadcasting our source IP should be either + // our own IP or 0.0.0.0 + + if (sip != inet_.ip_addr() && sip != IP4::INADDR_ANY) { + debug2(" Dropping outbound broadcast packet due to " + "invalid source IP %s\n", sip.str().c_str()); + return; + } + // mui importante + dest_mac = Ethernet::addr::BROADCAST_FRAME; + + } else { + if (sip != inet_.ip_addr()) { + debug2(" physical> Not bound to source IP %s. My IP is %s. DROP!\n", + sip.str().c_str(), inet_.ip_addr().str().c_str()); + return; + } + + // If we don't have a cached IP, perform address resolution + if (!is_valid_cached(dip)) { + arp_resolver_(pckt); + return; + } + + // Get MAC from cache + dest_mac = cache_[dip].mac_; + } + + /** Attach next-hop mac and ethertype to ethernet header */ + Ethernet::header* ethhdr = reinterpret_cast(pckt->buffer()); + ethhdr->src = mac_; + ethhdr->dest = dest_mac; + ethhdr->type = Ethernet::ETH_IP4; + + debug2(" physical> Sending packet to %s\n", mac_.str().c_str()); + linklayer_out_(pckt); + } + + void Arp::await_resolution(Packet_ptr pckt, IP4::addr) { + auto queue = waiting_packets_.find(pckt->next_hop()); + + if (queue != waiting_packets_.end()) { + debug(" Packets already queueing for this IP\n"); + queue->second->chain(pckt); + } else { + debug(" This is the first packet going to that IP\n"); + waiting_packets_.emplace(std::make_pair(pckt->next_hop(), pckt)); + } + } + + void Arp::arp_resolve(Packet_ptr pckt) { + debug(" %s\n", pckt->next_hop().str().c_str()); + + await_resolution(pckt, pckt->next_hop()); + + auto req = view_packet_as(inet_.createPacket(sizeof(header))); + req->init(mac_, inet_.ip_addr()); + + req->set_dest_mac(Ethernet::addr::BROADCAST_FRAME); + req->set_dest_ip(pckt->next_hop()); + req->set_opcode(H_request); + + linklayer_out_(req); + } + + void Arp::hh_map(Packet_ptr pckt) { + (void) pckt; + debug("ARP-resolution using the HH-hack"); + /** + // Fixed mac prefix + mac.minor = 0x01c0; //Big-endian c001 + // Destination IP + mac.major = dip.whole; + debug("ARP cache missing. Guessing Mac %s from next-hop IP: %s (dest.ip: %s)", + mac.str().c_str(), dip.str().c_str(), iphdr->daddr.str().c_str()); + **/ + } + +} //< namespace net diff --git a/src/net/ip4/icmpv4.cpp b/src/net/ip4/icmpv4.cpp index 5a7b4a113f..5c7b589dfd 100644 --- a/src/net/ip4/icmpv4.cpp +++ b/src/net/ip4/icmpv4.cpp @@ -24,68 +24,68 @@ namespace net { -ICMPv4::ICMPv4(Inet& inet) : - inet_{inet} + ICMPv4::ICMPv4(Inet& inet) : + inet_{inet} {} -void ICMPv4::bottom(Packet_ptr pckt) { - if (pckt->size() < sizeof(full_header)) // Drop if not a full header - return; + void ICMPv4::bottom(Packet_ptr pckt) { + if (pckt->size() < sizeof(full_header)) // Drop if not a full header + return; - full_header* full_hdr = reinterpret_cast(pckt->buffer()); - icmp_header* hdr = &full_hdr->icmp_hdr; + full_header* full_hdr = reinterpret_cast(pckt->buffer()); + icmp_header* hdr = &full_hdr->icmp_hdr; #ifdef DEBUG - auto ip_address = full_hdr->ip_hdr.saddr.str().c_str(); + auto ip_address = full_hdr->ip_hdr.saddr.str().c_str(); #endif - switch(hdr->type) { - case (ICMP_ECHO): - debug(" PING from %s\n", ip_address); - ping_reply(full_hdr, pckt->size()); - break; - case (ICMP_ECHO_REPLY): - debug(" PING Reply from %s\n", ip_address); - break; + switch(hdr->type) { + case (ICMP_ECHO): + debug(" PING from %s\n", ip_address); + ping_reply(full_hdr, pckt->size()); + break; + case (ICMP_ECHO_REPLY): + debug(" PING Reply from %s\n", ip_address); + break; + } } -} -void ICMPv4::ping_reply(full_header* full_hdr, uint16_t size) { - auto packet_ptr = inet_.createPacket(size); - auto buf = packet_ptr->buffer(); + void ICMPv4::ping_reply(full_header* full_hdr, uint16_t size) { + auto packet_ptr = inet_.createPacket(size); + auto buf = packet_ptr->buffer(); - icmp_header* hdr = &reinterpret_cast(buf)->icmp_hdr; - hdr->type = ICMP_ECHO_REPLY; - hdr->code = 0; - hdr->identifier = full_hdr->icmp_hdr.identifier; - hdr->sequence = full_hdr->icmp_hdr.sequence; + icmp_header* hdr = &reinterpret_cast(buf)->icmp_hdr; + hdr->type = ICMP_ECHO_REPLY; + hdr->code = 0; + hdr->identifier = full_hdr->icmp_hdr.identifier; + hdr->sequence = full_hdr->icmp_hdr.sequence; - debug(" Rest of header IN: 0x%lx OUT: 0x%lx\n", - full_hdr->icmp_hdr.rest, hdr->rest); + debug(" Rest of header IN: 0x%lx OUT: 0x%lx\n", + full_hdr->icmp_hdr.rest, hdr->rest); - debug(" Transmitting answer\n"); + debug(" Transmitting answer\n"); - // Populate response IP header - auto ip4_pckt = std::static_pointer_cast(packet_ptr); - ip4_pckt->init(); - ip4_pckt->set_src(full_hdr->ip_hdr.daddr); - ip4_pckt->set_dst(full_hdr->ip_hdr.saddr); - ip4_pckt->set_protocol(IP4::IP4_ICMP); + // Populate response IP header + auto ip4_pckt = std::static_pointer_cast(packet_ptr); + ip4_pckt->init(); + ip4_pckt->set_src(full_hdr->ip_hdr.daddr); + ip4_pckt->set_dst(full_hdr->ip_hdr.saddr); + ip4_pckt->set_protocol(IP4::IP4_ICMP); - // Copy payload from old to new packet - uint8_t* payload = reinterpret_cast(hdr) + sizeof(icmp_header); - uint8_t* source = reinterpret_cast(&full_hdr->icmp_hdr) + sizeof(icmp_header); - memcpy(payload, source, size - sizeof(full_header)); + // Copy payload from old to new packet + uint8_t* payload = reinterpret_cast(hdr) + sizeof(icmp_header); + uint8_t* source = reinterpret_cast(&full_hdr->icmp_hdr) + sizeof(icmp_header); + memcpy(payload, source, size - sizeof(full_header)); - hdr->checksum = 0; - hdr->checksum = net::checksum(reinterpret_cast(hdr), - size - sizeof(full_header) + sizeof(icmp_header)); + hdr->checksum = 0; + hdr->checksum = net::checksum(reinterpret_cast(hdr), + size - sizeof(full_header) + sizeof(icmp_header)); - network_layer_out_(packet_ptr); -} + network_layer_out_(packet_ptr); + } -void icmp_default_out(Packet_ptr UNUSED(pckt)) { - debug(" No handler. DROP!\n"); -} + void icmp_default_out(Packet_ptr UNUSED(pckt)) { + debug(" No handler. DROP!\n"); + } } //< namespace net diff --git a/src/net/ip4/ip4.cpp b/src/net/ip4/ip4.cpp new file mode 100644 index 0000000000..5216833f90 --- /dev/null +++ b/src/net/ip4/ip4.cpp @@ -0,0 +1,110 @@ +// This file is a part of the IncludeOS unikernel - www.includeos.org +// +// Copyright 2015 Oslo and Akershus University College of Applied Sciences +// and Alfred Bratterud +// +// 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 DEBUG // Allow debugging +#define DEBUG2 // Allow debug lvl 2 +#include +#include +#include +#include + +namespace net { + + const IP4::addr IP4::INADDR_ANY(0); + const IP4::addr IP4::INADDR_BCAST(0xff,0xff,0xff,0xff); + + IP4::IP4(Inet& inet) noexcept: + stack_{inet} +{ + // Default gateway is addr 1 in the subnet. + // const uint32_t DEFAULT_GATEWAY = htonl(1); + // gateway_.whole = (local_ip_.whole & netmask_.whole) | DEFAULT_GATEWAY; +} + + void IP4::bottom(Packet_ptr pckt) { + debug2(" got the data.\n"); + + auto data = pckt->buffer(); + ip_header* hdr = &reinterpret_cast(data)->ip_hdr; + + debug2("\t Source IP: %s Dest.IP: %s\n", + hdr->saddr.str().c_str(), hdr->daddr.str().c_str()); + + switch(hdr->protocol){ + case IP4_ICMP: + debug2("\t Type: ICMP\n"); + icmp_handler_(pckt); + break; + case IP4_UDP: + debug2("\t Type: UDP\n"); + udp_handler_(pckt); + break; + case IP4_TCP: + tcp_handler_(pckt); + debug2("\t Type: TCP\n"); + break; + default: + debug("\t Type: UNKNOWN %i\n", hdr->protocol); + break; + } + } + + uint16_t IP4::checksum(ip_header* hdr) { + return net::checksum(reinterpret_cast(hdr), sizeof(ip_header)); + } + + void IP4::transmit(Packet_ptr pckt) { + assert(pckt->size() > sizeof(IP4::full_header)); + + auto ip4_pckt = std::static_pointer_cast(pckt); + ip4_pckt->make_flight_ready(); + + IP4::ip_header& hdr = ip4_pckt->ip4_header(); + // Create local and target subnets + addr target = hdr.daddr & stack_.netmask(); + addr local = stack_.ip_addr() & stack_.netmask(); + + // Compare subnets to know where to send packet + pckt->next_hop(target == local ? hdr.daddr : stack_.router()); + + debug(" Next hop for %s, (netmask %s, local IP: %s, gateway: %s) == %s\n", + hdr.daddr.str().c_str(), + stack_.netmask().str().c_str(), + stack_.ip_addr().str().c_str(), + stack_.router().str().c_str(), + target == local ? "DIRECT" : "GATEWAY"); + + debug(" my ip: %s, Next hop: %s, Packet size: %i IP4-size: %i\n", + stack_.ip_addr().str().c_str(), + pckt->next_hop().str().c_str(), + pckt->size(), + ip4_pckt->ip4_segment_size() + ); + + linklayer_out_(pckt); + } + + // Empty handler for delegates initialization + void ignore_ip4_up(Packet_ptr UNUSED(pckt)) { + debug(" Empty handler. Ignoring.\n"); + } + + void ignore_ip4_down(Packet_ptr UNUSED(pckt)) { + debug("Link layer> No handler - DROP!\n"); + } + +} //< namespace net diff --git a/src/net/ip4/udp.cpp b/src/net/ip4/udp.cpp index 7ef9208f86..95a0d2026e 100644 --- a/src/net/ip4/udp.cpp +++ b/src/net/ip4/udp.cpp @@ -6,9 +6,9 @@ // 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. @@ -21,72 +21,181 @@ #include #include +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) + namespace net { -void UDP::bottom(Packet_ptr pckt) -{ - debug(" Got data"); - std::shared_ptr udp = + UDP::UDP(Stack& inet) + : stack_(inet) + { + network_layer_out_ = [] (net::Packet_ptr) {}; + inet.on_transmit_queue_available( + transmit_avail_delg::from(this)); + } + + void UDP::bottom(net::Packet_ptr pckt) + { + std::shared_ptr udp = std::static_pointer_cast (pckt); - - debug("\t Source port: %i, Dest. Port: %i Length: %i\n", - udp->src_port(), udp->dst_port(), udp->length()); - - auto it = ports_.find(udp->dst_port()); - if (it != ports_.end()) + + debug("\t Source port: %i, Dest. Port: %i Length: %i\n", + udp->src_port(), udp->dst_port(), udp->length()); + + auto it = ports_.find(udp->dst_port()); + if (it != ports_.end()) + { + debug(" Someone's listening to this port. Forwarding...\n"); + it->second.internal_read(udp); + return; + } + + debug(" Nobody's listening to this port. Drop!\n"); + } + + UDPSocket& UDP::bind(UDP::port_t port) { - debug(" Someone's listening to this port. Forwarding...\n"); - it->second.internal_read(udp); + debug(" Binding to port %i\n", port); + /// ... !!! + auto it = ports_.find(port); + if (likely(it == ports_.end())) { + // create new socket + auto res = ports_.emplace( + std::piecewise_construct, + std::forward_as_tuple(port), + std::forward_as_tuple(*this, port)); + it = res.first; + } + return it->second; + } + + UDPSocket& UDP::bind() { + + if (ports_.size() >= 0xfc00) + panic("UPD Socket: All ports taken!"); + + debug("UDP finding free ephemeral port\n"); + while (ports_.find(++current_port_) != ports_.end()) + // prevent automatic ports under 1024 + if (current_port_ == 0) current_port_ = 1024; + + debug("UDP binding to %i port\n", current_port_); + return bind(current_port_); + } + + void UDP::transmit(UDP::Packet_ptr udp) { + debug2(" Transmitting %i bytes (seg=%i) from %s to %s:%i\n", + udp->length(), udp->ip4_segment_size(), + udp->src().str().c_str(), + udp->dst().str().c_str(), udp->dst_port()); + + assert(udp->length() >= sizeof(udp_header)); + assert(udp->protocol() == IP4::IP4_UDP); + + auto pckt = Packet::packet(udp); + network_layer_out_(pckt); + } + + void UDP::flush() + { + size_t packets = stack_.transmit_queue_available(); + if (packets) process_sendq(packets); + //else debug(" Transmit queue full. Waiting for offer"); + } - - debug(" Nobody's listening to this port. Drop!\n"); -} - -UDP::Socket& UDP::bind(UDP::port_t port) -{ - debug(" Binding to port %i\n", port); - /// ... !!! - auto it = ports_.find(port); - if (it == ports_.end()) { - // create new socket - auto res = ports_.emplace( - std::piecewise_construct, - std::forward_as_tuple(port), - std::forward_as_tuple(stack_, port)); - it = res.first; + + void UDP::process_sendq(size_t num) + { + while (!sendq.empty() && num != 0) + { + WriteBuffer& buffer = sendq.front(); + + // create and transmit packet from writebuffer + buffer.write(); + num--; + + if (buffer.done()) + { + auto copy = buffer.callback; + // remove buffer from queue + sendq.pop_front(); + // call on_written callback + copy(); + // reduce @num, just in case packets were sent in + // another stack frame + size_t avail = stack_.transmit_queue_available(); + num = (num > avail) ? avail : num; + } + } + } + + size_t UDP::WriteBuffer::packets_needed() const + { + int r = remaining(); + // whole packets + size_t P = r / udp.max_datagram_size(); + // one packet for remainder + if (r % udp.max_datagram_size()) P++; + return P; + } + UDP::WriteBuffer::WriteBuffer( + const uint8_t* data, size_t length, sendto_handler cb, + UDP& stack, addr_t LA, port_t LP, addr_t DA, port_t DP) + : len(length), offset(0), callback(cb), udp(stack), + l_addr(LA), l_port(LP), d_port(DP), d_addr(DA) + { + // create a copy of the data, + auto* copy = new uint8_t[len]; + memcpy(copy, data, length); + // make it shared + this->buf = + std::shared_ptr (copy, std::default_delete()); + } + + void UDP::WriteBuffer::write() + { + + // the bytes remaining to be written + UDP::Packet_ptr chain_head{}; + + debug(" %i bytes to write, need %i packets \n", + remaining(), remaining() / udp.max_datagram_size() + (remaining() % udp.max_datagram_size() ? 1 : 0)); + + do { + size_t total = remaining(); + total = (total > udp.max_datagram_size()) ? udp.max_datagram_size() : total; + + // create some packet p (and convert it to PacketUDP) + auto p = udp.stack().createPacket(0); + // fill buffer (at payload position) + memcpy(p->buffer() + PacketUDP::HEADERS_SIZE, + buf.get() + this->offset, total); + + // initialize packet with several infos + auto p2 = std::static_pointer_cast(p); + + p2->init(); + p2->header().sport = htons(l_port); + p2->header().dport = htons(d_port); + p2->set_src(l_addr); + p2->set_dst(d_addr); + p2->set_length(total); + + // Attach packet to chain + if (!chain_head) + chain_head = p2; + else + chain_head->chain(p2); + + // next position in buffer + this->offset += total; + + } while ( remaining() ); + + // ship the packet + udp.transmit(chain_head); + + } - return it->second; -} - -UDP::Socket& UDP::bind() { - - if (ports_.size() >= 0xfc00) - panic("UPD Socket: All ports taken!"); - - debug("UDP finding free ephemeral port\n"); - while (ports_.find(++current_port_) != ports_.end()) - if (current_port_ == 0) current_port_ = 1025; // prevent automatic ports under 1024 - - debug("UDP binding to %i port\n", current_port_); - return bind(current_port_); -} - -void UDP::transmit(std::shared_ptr udp) { - debug2(" Transmitting %i bytes (seg=%i) from %s to %s:%i\n", - udp->length(), udp->ip4_segment_size(), - udp->src().str().c_str(), - udp->dst().str().c_str(), udp->dst_port()); - - assert(udp->length() >= sizeof(UDP::udp_header)); - assert(udp->protocol() == IP4::IP4_UDP); - - Packet_ptr pckt = Packet::packet(udp); - network_layer_out_(pckt); -} - -void ignore_udp(Packet_ptr) -{ - debug("Network> No handler - DROP!\n"); -} } //< namespace net diff --git a/src/net/ip4/udp_socket.cpp b/src/net/ip4/udp_socket.cpp index 46ebfbc8fd..3622ed85c2 100644 --- a/src/net/ip4/udp_socket.cpp +++ b/src/net/ip4/udp_socket.cpp @@ -6,9 +6,9 @@ // 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. @@ -18,18 +18,21 @@ #include #include +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) + namespace net { - Socket::Socket(Inet& _stack, port port) - : stack(_stack), l_port(port) {} - - int Socket::internal_read(std::shared_ptr udp) - { - return on_read(*this, udp->src(), udp->src_port(), udp->data(), udp->data_length()); - } - - void Socket::packet_init(std::shared_ptr p, - addr srcIP, addr destIP, port port, uint16_t length) + UDPSocket::UDPSocket(UDP& udp_, port_t port) + : udp(udp_), l_port(port) + {} + + void UDPSocket::packet_init( + UDP::Packet_ptr p, + addr_t srcIP, + addr_t destIP, + port_t port, + uint16_t length) { p->init(); p->header().sport = htons(this->l_port); @@ -37,63 +40,49 @@ namespace net p->set_src(srcIP); p->set_dst(destIP); p->set_length(length); - + assert(p->data_length() == length); } - - int Socket::internal_write(addr srcIP, addr destIP, - port port, const uint8_t* buffer, int length) + + void UDPSocket::internal_read(UDP::Packet_ptr udp) { - // the maximum we can write per packet: - const int WRITE_MAX = stack.MTU() - PacketUDP::HEADERS_SIZE; - // the bytes remaining to be written - int rem = length; - - while (rem >= WRITE_MAX) - { - // create some packet p (and convert it to PacketUDP) - auto p = stack.createPacket(stack.MTU()); - // fill buffer (at payload position) - memcpy(p->buffer() + PacketUDP::HEADERS_SIZE, buffer, WRITE_MAX); - - // initialize packet with several infos - auto p2 = std::static_pointer_cast(p); - packet_init(p2, srcIP, destIP, port, WRITE_MAX); - // ship the packet - stack.udp().transmit(p2); - - // next buffer part - buffer += WRITE_MAX; rem -= WRITE_MAX; - } - if (rem) - { - // copy remainder - size_t size = PacketUDP::HEADERS_SIZE + rem; - - // create some packet p - auto p = stack.createPacket(size); - memcpy(p->buffer() + PacketUDP::HEADERS_SIZE, buffer, rem); - - // initialize packet with several infos - auto p2 = std::static_pointer_cast(p); - packet_init(p2, srcIP, destIP, port, rem); - // ship the packet - stack.udp().transmit(p2); - } - return length; - } // internal_write() - - int Socket::sendto(addr destIP, port port, - const void* buffer, int len) + on_read_handler( + udp->src(), udp->src_port(), udp->data(), udp->data_length()); + } + + void UDPSocket::sendto( + addr_t destIP, + port_t port, + const void* buffer, + size_t len, + sendto_handler cb) { - return internal_write(local_addr(), destIP, port, - (const uint8_t*) buffer, len); + if (likely(len)) + { + udp.sendq.emplace_back( + (const uint8_t*) buffer, len, cb, this->udp, + local_addr(), this->l_port, destIP, port); + + // UDP packets are meant to be sent immediately, so try flushing + udp.flush(); + } } - int Socket::bcast(addr srcIP, port port, - const void* buffer, int len) + void UDPSocket::bcast( + addr_t srcIP, + port_t port, + const void* buffer, + size_t len, + sendto_handler cb) { - return internal_write(srcIP, IP4::INADDR_BCAST, port, - (const uint8_t*) buffer, len); + if (likely(len)) + { + udp.sendq.emplace_back( + (const uint8_t*) buffer, len, cb, this->udp, + srcIP, this->l_port, IP4::INADDR_BCAST, port); + + // UDP packets are meant to be sent immediately, so try flushing + udp.flush(); + } } - + } diff --git a/src/net/ip6/icmp6.cpp b/src/net/ip6/icmp6.cpp index b58907e3b1..1ee406fcaa 100644 --- a/src/net/ip6/icmp6.cpp +++ b/src/net/ip6/icmp6.cpp @@ -44,92 +44,92 @@ namespace net std::string ICMPv6::code_string(uint8_t type, uint8_t code) { switch (type) - { - /// error codes /// - case 1: - /// delivery problems /// - switch (code) { - case 0: - return "No route to destination"; + /// error codes /// case 1: - return "Communication with dest administratively prohibited"; + /// delivery problems /// + switch (code) + { + case 0: + return "No route to destination"; + case 1: + return "Communication with dest administratively prohibited"; + case 2: + return "Beyond scope of source address"; + case 3: + return "Address unreachable"; + case 4: + return "Port unreachable"; + case 5: + return "Source address failed ingress/egress policy"; + case 6: + return "Reject route to destination"; + case 7: + return "Error in source routing header"; + default: + return "ERROR Invalid ICMP type"; + } case 2: - return "Beyond scope of source address"; + /// size problems /// + return "Packet too big"; + case 3: - return "Address unreachable"; + /// time problems /// + switch (code) + { + case 0: + return "Hop limit exceeded in traffic"; + case 1: + return "Fragment reassembly time exceeded"; + default: + return "ERROR Invalid ICMP code"; + } case 4: - return "Port unreachable"; - case 5: - return "Source address failed ingress/egress policy"; - case 6: - return "Reject route to destination"; - case 7: - return "Error in source routing header"; - default: - return "ERROR Invalid ICMP type"; - } - case 2: - /// size problems /// - return "Packet too big"; + /// parameter problems /// + switch (code) + { + case 0: + return "Erroneous header field"; + case 1: + return "Unrecognized next header"; + case 2: + return "Unrecognized IPv6 option"; + default: + return "ERROR Invalid ICMP code"; + } - case 3: - /// time problems /// - switch (code) - { - case 0: - return "Hop limit exceeded in traffic"; - case 1: - return "Fragment reassembly time exceeded"; - default: - return "ERROR Invalid ICMP code"; - } - case 4: - /// parameter problems /// - switch (code) - { - case 0: - return "Erroneous header field"; - case 1: - return "Unrecognized next header"; - case 2: - return "Unrecognized IPv6 option"; - default: - return "ERROR Invalid ICMP code"; - } - - /// echo feature /// - case ECHO_REQUEST: - return "Echo request"; - case ECHO_REPLY: - return "Echo reply"; + /// echo feature /// + case ECHO_REQUEST: + return "Echo request"; + case ECHO_REPLY: + return "Echo reply"; - /// multicast feature /// - case 130: - return "Multicast listener query"; - case 131: - return "Multicast listener report"; - case 132: - return "Multicast listener done"; + /// multicast feature /// + case 130: + return "Multicast listener query"; + case 131: + return "Multicast listener report"; + case 132: + return "Multicast listener done"; - /// neighbor discovery protocol /// - case ND_ROUTER_SOL: - return "NDP Router solicitation request"; - case ND_ROUTER_ADV: - return "NDP Router advertisement"; - case ND_NEIGHB_SOL: - return "NDP Neighbor solicitation request"; - case ND_NEIGHB_ADV: - return "NDP Neighbor advertisement"; - case ND_REDIRECT: - return "NDP Redirect message"; + /// neighbor discovery protocol /// + case ND_ROUTER_SOL: + return "NDP Router solicitation request"; + case ND_ROUTER_ADV: + return "NDP Router advertisement"; + case ND_NEIGHB_SOL: + return "NDP Neighbor solicitation request"; + case ND_NEIGHB_ADV: + return "NDP Neighbor advertisement"; + case ND_REDIRECT: + return "NDP Redirect message"; - case 143: - return "Multicast Listener Discovery (MLDv2) reports (RFC 3810)"; + case 143: + return "Multicast Listener Discovery (MLDv2) reports (RFC 3810)"; - default: - return "Unknown type: " + std::to_string((int) type); - } + default: + return "Unknown type: " + std::to_string((int) type); + } } int ICMPv6::bottom(Packet_ptr pckt) @@ -139,27 +139,27 @@ namespace net type_t type = icmp->type(); if (listeners.find(type) != listeners.end()) - { - return listeners[type](*this, icmp); - } + { + return listeners[type](*this, icmp); + } else - { - debug(">>> IPv6 -> ICMPv6 bottom (no handler installed)\n"); - debug("ICMPv6 type %d: %s\n", - (int) icmp->type(), code_string(icmp->type(), icmp->code()).c_str()); + { + debug(">>> IPv6 -> ICMPv6 bottom (no handler installed)\n"); + debug("ICMPv6 type %d: %s\n", + (int) icmp->type(), code_string(icmp->type(), icmp->code()).c_str()); - /* - // show correct checksum - intptr_t chksum = icmp->checksum(); - debug("ICMPv6 checksum: %p \n",(void*) chksum); + /* + // show correct checksum + intptr_t chksum = icmp->checksum(); + debug("ICMPv6 checksum: %p \n",(void*) chksum); - // show our recalculated checksum - icmp->header().checksum_ = 0; - chksum = checksum(icmp); - debug("ICMPv6 our estimate: %p \n", (void*) chksum ); - */ - return -1; - } + // show our recalculated checksum + icmp->header().checksum_ = 0; + chksum = checksum(icmp); + debug("ICMPv6 our estimate: %p \n", (void*) chksum ); + */ + return -1; + } } int ICMPv6::transmit(std::shared_ptr& pckt) { @@ -189,18 +189,18 @@ namespace net //assert(hdr.next() == 58); // ICMPv6 /** - RFC 4443 - 2.3. Message Checksum Calculation + RFC 4443 + 2.3. Message Checksum Calculation - The checksum is the 16-bit one's complement of the one's complement - sum of the entire ICMPv6 message, starting with the ICMPv6 message - type field, and prepended with a "pseudo-header" of IPv6 header - fields, as specified in [IPv6, Section 8.1]. The Next Header value - used in the pseudo-header is 58. (The inclusion of a pseudo-header - in the ICMPv6 checksum is a change from IPv4; see [IPv6] for the - rationale for this change.) + The checksum is the 16-bit one's complement of the one's complement + sum of the entire ICMPv6 message, starting with the ICMPv6 message + type field, and prepended with a "pseudo-header" of IPv6 header + fields, as specified in [IPv6, Section 8.1]. The Next Header value + used in the pseudo-header is 58. (The inclusion of a pseudo-header + in the ICMPv6 checksum is a change from IPv4; see [IPv6] for the + rationale for this change.) - For computing the checksum, the checksum field is first set to zero. + For computing the checksum, the checksum field is first set to zero. **/ union { @@ -214,7 +214,7 @@ namespace net uint16_t* it_end = it + sizeof(pseudo_header) / 2; while (it < it_end) - sum.whole += *(it++); + sum.whole += *(it++); // compute sum of data it = (uint16_t*) pckt->payload(); @@ -243,21 +243,21 @@ namespace net icmp->type = ICMPv6::ECHO_REPLY; if (pckt->dst().is_multicast()) - { - // We won't be changing source address for multicast ping - debug("Was multicast ping6: no change for source and dest\n"); - } + { + // We won't be changing source address for multicast ping + debug("Was multicast ping6: no change for source and dest\n"); + } else - { - printf("Normal ping6: source is us\n"); - printf("src is %s\n", pckt->src().str().c_str()); - printf("dst is %s\n", pckt->dst().str().c_str()); + { + printf("Normal ping6: source is us\n"); + printf("src is %s\n", pckt->src().str().c_str()); + printf("dst is %s\n", pckt->dst().str().c_str()); - printf("multicast is %s\n", IP6::addr::link_all_nodes.str().c_str()); - // normal ping: send packet to source, from us - pckt->set_dst(pckt->src()); - pckt->set_src(caller.local_ip()); - } + printf("multicast is %s\n", IP6::addr::link_all_nodes.str().c_str()); + // normal ping: send packet to source, from us + pckt->set_dst(pckt->src()); + pckt->set_src(caller.local_ip()); + } // calculate and set checksum // NOTE: do this after changing packet contents! icmp->checksum = 0; @@ -288,9 +288,9 @@ namespace net // ether-broadcast an IPv6 packet to all routers // IPv6mcast_02: 33:33:00:00:00:02 auto pckt = IP6::create( - IP6::PROTO_ICMPv6, - Ethernet::addr::IPv6mcast_02, - IP6::addr::link_unspecified); + IP6::PROTO_ICMPv6, + Ethernet::addr::IPv6mcast_02, + IP6::addr::link_unspecified); // RFC4861 4.1. Router Solicitation Message Format pckt->set_hoplimit(255); diff --git a/src/net/ip6/ip6.cpp b/src/net/ip6/ip6.cpp index 029f3657a3..dab269a491 100644 --- a/src/net/ip6/ip6.cpp +++ b/src/net/ip6/ip6.cpp @@ -47,35 +47,35 @@ namespace net uint8_t IP6::parse6(uint8_t*& reader, uint8_t next) { switch (next) - { - case PROTO_HOPOPT: - case PROTO_OPTSv6: - { - debug(">>> IPv6 options header %s", protocol_name(next).c_str()); + { + case PROTO_HOPOPT: + case PROTO_OPTSv6: + { + debug(">>> IPv6 options header %s", protocol_name(next).c_str()); - options_header& opts = *(options_header*) reader; - reader += opts.size(); + options_header& opts = *(options_header*) reader; + reader += opts.size(); - debug("OPTSv6 size: %d\n", opts.size()); - debug("OPTSv6 ext size: %d\n", opts.extended()); + debug("OPTSv6 size: %d\n", opts.size()); + debug("OPTSv6 ext size: %d\n", opts.extended()); - next = opts.next(); - debug("OPTSv6 next: %s\n", protocol_name(next).c_str()); - } break; - case PROTO_ICMPv6: - break; - case PROTO_UDP: - break; + next = opts.next(); + debug("OPTSv6 next: %s\n", protocol_name(next).c_str()); + } break; + case PROTO_ICMPv6: + break; + case PROTO_UDP: + break; - default: - debug("Not parsing: %s\n", protocol_name(next).c_str()); - } + default: + debug("Not parsing: %s\n", protocol_name(next).c_str()); + } return next; } - void IP6::bottom(Packet_ptr pckt) - { + void IP6::bottom(Packet_ptr pckt) + { debug(">>> IPv6 packet:"); @@ -92,20 +92,20 @@ namespace net uint8_t next = hdr.next(); while (next != PROTO_NoNext) - { - auto it = proto_handlers.find(next); - if (it != proto_handlers.end()) { - // forward packet to handler - pckt->set_payload(reader); - it->second(pckt); + auto it = proto_handlers.find(next); + if (it != proto_handlers.end()) + { + // forward packet to handler + pckt->set_payload(reader); + it->second(pckt); + } + else + // just print information + next = parse6(reader, next); } - else - // just print information - next = parse6(reader, next); - } - }; + }; static const std::string lut = "0123456789abcdef"; @@ -117,12 +117,12 @@ namespace net const uint8_t* octet = i8; for (int i = 0; i < 16; i++) - { - ret[counter++] = lut[(octet[i] & 0xF0) >> 4]; - ret[counter++] = lut[(octet[i] & 0x0F) >> 0]; - if (i & 1) - ret[counter++] = ':'; - } + { + ret[counter++] = lut[(octet[i] & 0xF0) >> 4]; + ret[counter++] = lut[(octet[i] & 0x0F) >> 0]; + if (i & 1) + ret[counter++] = ':'; + } ret.resize(counter-1); return ret; } @@ -138,7 +138,7 @@ namespace net } std::shared_ptr IP6::create(uint8_t proto, - Ethernet::addr ether_dest, const IP6::addr& ip6_dest) + Ethernet::addr ether_dest, const IP6::addr& ip6_dest) { // arbitrarily big buffer uint8_t* data = new uint8_t[1500]; diff --git a/src/net/ip6/ndp.cpp b/src/net/ip6/ndp.cpp index 3faa99847d..7dde0dcbfc 100644 --- a/src/net/ip6/ndp.cpp +++ b/src/net/ip6/ndp.cpp @@ -6,9 +6,9 @@ // 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. @@ -16,4 +16,3 @@ // limitations under the License. #include - diff --git a/src/net/ip6/udp6.cpp b/src/net/ip6/udp6.cpp index e89aaf33dd..efe24fbea4 100644 --- a/src/net/ip6/udp6.cpp +++ b/src/net/ip6/udp6.cpp @@ -35,10 +35,10 @@ namespace net // check for listeners on dst port if (listeners.find(port) != listeners.end()) - { - // make the call to the listener on that port - return listeners[port](P6); - } + { + // make the call to the listener on that port + return listeners[port](P6); + } // was not forwarded, so just return -1 debug("... dumping packet, no listeners\n"); return -1; @@ -83,7 +83,7 @@ namespace net // normally we would start at &icmp_echo::type, but // it is after all the first element of the icmp message memcpy(data + sizeof(UDPv6::pseudo_header), this->payload(), - datalen - sizeof(UDPv6::pseudo_header)); + datalen - sizeof(UDPv6::pseudo_header)); // calculate csum and free data on return header().chksum = net::checksum(data, datalen); @@ -91,7 +91,7 @@ namespace net } std::shared_ptr UDPv6::create( - Ethernet::addr ether_dest, const IP6::addr& ip6_dest, UDPv6::port_t port) + Ethernet::addr ether_dest, const IP6::addr& ip6_dest, UDPv6::port_t port) { auto packet = IP6::create(IP6::PROTO_UDP, ether_dest, ip6_dest); auto udp_packet = view_packet_as (packet); diff --git a/src/net/packet.cpp b/src/net/packet.cpp index 391499764d..1b20ad2fca 100644 --- a/src/net/packet.cpp +++ b/src/net/packet.cpp @@ -6,9 +6,9 @@ // 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. @@ -22,42 +22,42 @@ namespace net { -Packet::Packet(BufferStore::buffer_t buf, size_t bufsize, size_t datalen, release_del rel) noexcept: + Packet::Packet(BufferStore::buffer_t buf, size_t bufsize, size_t datalen, release_del rel) noexcept: buf_ {buf}, - capacity_ {bufsize}, - size_ {datalen}, - next_hop4_ {}, - release_ {rel} + capacity_ {bufsize}, + size_ {datalen}, + next_hop4_ {}, + release_ {rel} { debug(" CONSTRUCT packet, buf @ %p\n", buf); } -Packet::~Packet() { - debug(" DESTRUCT packet, buf @ %p\n", buf_); - release_(buf_, capacity_); -} - -IP4::addr Packet::next_hop() const noexcept { - return next_hop4_; -} + Packet::~Packet() { + debug(" DESTRUCT packet, buf @ %p\n", buf_); + release_(buf_, capacity_); + } -void Packet::next_hop(IP4::addr ip) noexcept { - next_hop4_ = ip; -} + IP4::addr Packet::next_hop() const noexcept { + return next_hop4_; + } -int Packet::set_size(const size_t size) noexcept { - if(size > capacity_) { - return 0; + void Packet::next_hop(IP4::addr ip) noexcept { + next_hop4_ = ip; } - size_ = size; + int Packet::set_size(const size_t size) noexcept { + if(size > capacity_) { + return 0; + } - return size_; -} + size_ = size; -void default_release(BufferStore::buffer_t b, size_t) { - (void) b; - debug(" Ignoring buffer."); -} + return size_; + } + + void default_release(BufferStore::buffer_t b, size_t) { + (void) b; + debug(" Ignoring buffer."); + } } //< namespace net diff --git a/src/net/tcp.cpp b/src/net/tcp.cpp index af329e5461..ef8782afb8 100644 --- a/src/net/tcp.cpp +++ b/src/net/tcp.cpp @@ -6,9 +6,9 @@ // 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. @@ -18,223 +18,277 @@ #define DEBUG2 #include +#include using namespace std; using namespace net; -TCP::TCP(IPStack& inet) : - inet_(inet), - listeners_(), - connections_(), - MAX_SEG_LIFETIME(30s) +TCP::TCP(IPStack& inet) : + inet_(inet), + listeners_(), + connections_(), + writeq(), + used_ports(), + MAX_SEG_LIFETIME(30s) { - + inet.on_transmit_queue_available(transmit_avail_delg::from(this)); } /* - Note: There is different approaches to how to handle listeners & connections. - Need to discuss and decide for the best one. + Note: There is different approaches to how to handle listeners & connections. + Need to discuss and decide for the best one. - Best solution(?): - Preallocate a pool with listening connections. - When threshold is reach, remove/add new ones, similar to TCP window. + Best solution(?): + Preallocate a pool with listening connections. + When threshold is reach, remove/add new ones, similar to TCP window. - Current solution: - Simple. + Current solution: + Simple. */ TCP::Connection& TCP::bind(Port port) { - auto listen_conn_it = listeners_.find(port); - // Already a listening socket. - if(listen_conn_it != listeners_.end()) { - throw TCPException{"Port is already taken."}; - } - auto& connection = (listeners_.emplace(port, Connection{*this, port})).first->second; - debug(" Bound to port %i \n", port); - connection.open(false); - return connection; + // Already a listening socket. + if(listeners_.find(port) != listeners_.end()) { + throw TCPException{"Port is already taken."}; + } + auto& connection = (listeners_.emplace(port, Connection{*this, port})).first->second; + debug(" Bound to port %i \n", port); + connection.open(false); + return connection; } /* - Active open a new connection to the given remote. + Active open a new connection to the given remote. - @WARNING: Callback is added when returned (TCP::connect(...).onSuccess(...)), - and open() is called before callback is added. + @WARNING: Callback is added when returned (TCP::connect(...).onSuccess(...)), + and open() is called before callback is added. */ TCP::Connection_ptr TCP::connect(Socket remote) { - std::shared_ptr connection = add_connection(free_port(), remote); - connection->open(true); - return connection; + auto port = next_free_port(); + std::shared_ptr connection = add_connection(port, remote); + connection->open(true); + return connection; } /* - Active open a new connection to the given remote. + Active open a new connection to the given remote. */ void TCP::connect(Socket remote, Connection::ConnectCallback callback) { - auto connection = add_connection(free_port(), remote); - connection->onConnect(callback).open(true); + auto port = next_free_port(); + auto connection = add_connection(port, remote); + connection->onConnect(callback).open(true); } TCP::Seq TCP::generate_iss() { - // Do something to get a iss. - return rand(); + // Do something to get a iss. + return rand(); } /* - TODO: Check if there is any ports free. + TODO: Check if there is any ports free. */ -TCP::Port TCP::free_port() { - if(++current_ephemeral_ == 0) - current_ephemeral_ = 1025; - // Avoid giving a port that is bound to a service. - while(listeners_.find(current_ephemeral_) != listeners_.end()) - current_ephemeral_++; - - return current_ephemeral_; -} +TCP::Port TCP::next_free_port() { + if(++current_ephemeral_ == 0) { + current_ephemeral_ = 1025; + // TODO: Can be taken + } + // Avoid giving a port that is bound to a service. + while(listeners_.find(current_ephemeral_) != listeners_.end()) + current_ephemeral_++; -uint16_t TCP::checksum(TCP::Packet_ptr packet) { - // TCP header - TCP::Header* tcp_hdr = &(packet->header()); - // Pseudo header - TCP::Pseudo_header pseudo_hdr; - - int tcp_length = packet->tcp_length(); + return current_ephemeral_; +} - pseudo_hdr.saddr.whole = packet->src().whole; - pseudo_hdr.daddr.whole = packet->dst().whole; - pseudo_hdr.zero = 0; - pseudo_hdr.proto = IP4::IP4_TCP; - pseudo_hdr.tcp_length = htons(tcp_length); +/* + Expensive look up if port is in use. +*/ +bool TCP::port_in_use(const TCP::Port port) const { + if(listeners_.find(port) != listeners_.end()) + return true; + + for(auto conn : connections_) { + if(conn.first.first == port) + return true; + } + return false; +} - union { - uint32_t whole; - uint16_t part[2]; - } sum; - sum.whole = 0; +uint16_t TCP::checksum(TCP::Packet_ptr packet) { + // TCP header + TCP::Header* tcp_hdr = &(packet->header()); + // Pseudo header + TCP::Pseudo_header pseudo_hdr; + + int tcp_length = packet->tcp_length(); + + pseudo_hdr.saddr.whole = packet->src().whole; + pseudo_hdr.daddr.whole = packet->dst().whole; + pseudo_hdr.zero = 0; + pseudo_hdr.proto = IP4::IP4_TCP; + pseudo_hdr.tcp_length = htons(tcp_length); + + union Sum{ + uint32_t whole; + uint16_t part[2]; + } sum; + + sum.whole = 0; + + // Compute sum of pseudo header + for (uint16_t* it = (uint16_t*)&pseudo_hdr; it < (uint16_t*)&pseudo_hdr + sizeof(pseudo_hdr)/2; it++) + sum.whole += *it; + + // Compute sum sum the actual header and data + for (uint16_t* it = (uint16_t*)tcp_hdr; it < (uint16_t*)tcp_hdr + tcp_length/2; it++) + sum.whole+= *it; + + // The odd-numbered case + if (tcp_length & 1) { + debug(" ODD number of bytes. 0-pading \n"); + union { + uint16_t whole; + uint8_t part[2]; + } last_chunk; + last_chunk.part[0] = ((uint8_t*)tcp_hdr)[tcp_length - 1]; + last_chunk.part[1] = 0; + sum.whole += last_chunk.whole; + } + + debug2("(packet_ptr); + debug(" TCP Packet received - Source: %s, Destination: %s \n", + packet->source().to_string().c_str(), packet->destination().to_string().c_str()); + + // Do checksum + if(checksum(packet)) { + debug(" TCP Packet Checksum != 0 \n"); + } + + Connection::Tuple tuple { packet->dst_port(), packet->source() }; + + // Try to find the receiver + auto conn_it = connections_.find(tuple); + // Connection found + if(conn_it != connections_.end()) { + debug(" Connection found: %s \n", conn_it->second->to_string().c_str()); + conn_it->second->segment_arrived(packet); + } + // No connection found + else { + // Is there a listener? + auto listen_conn_it = listeners_.find(packet->dst_port()); + debug(" No connection found - looking for listener..\n"); + // Listener found => Create listening Connection + if(listen_conn_it != listeners_.end()) { + auto& listen_conn = listen_conn_it->second; + debug(" Listener found: %s ...\n", listen_conn.to_string().c_str()); + auto connection = (connections_.emplace(tuple, std::make_shared(listen_conn)).first->second); + // Set remote + connection->set_remote(packet->source()); + debug(" ... Creating connection: %s \n", connection->to_string().c_str()); + + connection->segment_arrived(packet); + } + // No listener found + else { + drop(packet); + } + } +} - // Compute sum sum the actual header and data - for (uint16_t* it = (uint16_t*)tcp_hdr; it < (uint16_t*)tcp_hdr + tcp_length/2; it++) - sum.whole+= *it; +void TCP::process_writeq(size_t packets) { + debug2(" size=%u p=%u\n", writeq.size(), packets); + // foreach connection who wants to write + while(packets and !writeq.empty()) { + auto conn = writeq.front(); + writeq.pop_back(); + conn->offer(packets); + conn->set_queued(false); + } +} - // The odd-numbered case - if (tcp_length & 1) { - debug(" ODD number of bytes. 0-pading \n"); - union { - uint16_t whole; - uint8_t part[2]; - } last_chunk; - last_chunk.part[0] = ((uint8_t*)tcp_hdr)[tcp_length - 1]; - last_chunk.part[1] = 0; - sum.whole += last_chunk.whole; - } +size_t TCP::send(Connection_ptr conn, const char* buffer, size_t n) { + size_t written{0}; + auto packets = inet_.transmit_queue_available(); - debug2(" Send request for %u bytes\n", n); - return ~(sum.part[0] + sum.part[1]); -} + if(packets > 0) { + written += conn->send(buffer, n, packets); + } + // if connection still can send (means there wasn't enough packets) + // only requeue if not already queued + if(conn->can_send() and !conn->is_queued()) { + debug2(" Conn queued.\n"); + writeq.push_back(conn); + conn->set_queued(true); + } -void TCP::bottom(net::Packet_ptr packet_ptr) { - // Translate into a TCP::Packet. This will be used inside the TCP-scope. - auto packet = std::static_pointer_cast(packet_ptr); - debug(" TCP Packet received - Source: %s, Destination: %s \n", - packet->source().to_string().c_str(), packet->destination().to_string().c_str()); - - // Do checksum - if(checksum(packet)) { - debug(" TCP Packet Checksum != 0 \n"); - } - - Connection::Tuple tuple { packet->dst_port(), packet->source() }; - - // Try to find the receiver - auto conn_it = connections_.find(tuple); - // Connection found - if(conn_it != connections_.end()) { - debug(" Connection found: %s \n", conn_it->second->to_string().c_str()); - conn_it->second->receive(packet); - } - // No connection found - else { - // Is there a listener? - auto listen_conn_it = listeners_.find(packet->dst_port()); - debug(" No connection found - looking for listener..\n"); - // Listener found => Create listening Connection - if(listen_conn_it != listeners_.end()) { - auto& listen_conn = listen_conn_it->second; - debug(" Listener found: %s ...\n", listen_conn.to_string().c_str()); - auto connection = (connections_.emplace(tuple, std::make_shared(listen_conn)).first->second); - // Set remote - connection->set_remote(packet->source()); - debug(" ... Creating connection: %s \n", connection->to_string().c_str()); - // Change to listening state. - //connection.open(); // already listening - connection->receive(packet); - } - // No listener found - else { - drop(packet); - } - } + return written; } /* - Show all connections for TCP as a string. + Show all connections for TCP as a string. + + Format: + [Protocol][Recv][Send][Local][Remote][State] - Format: - [Protocol][Recv][Send][Local][Remote][State] + TODO: Make sure Recv, Send, In, Out is correct and add them to output. Also, alignment? */ -string TCP::status() const { - // Write all connections in a cute list. - stringstream ss; - ss << "LISTENING SOCKETS:\n"; - for(auto listen_it : listeners_) { - ss << listen_it.second.to_string() << "\n"; - } - ss << "\nCONNECTIONS:\n" << "Proto\tRecv\tSend\tIn\tOut\tLocal\t\t\tRemote\t\t\tState\n"; - for(auto con_it : connections_) { - auto& c = *(con_it.second); - ss << "tcp4\t" - << " " << "\t" << " " << "\t" - << c.bytes_received() << "\t" << c.bytes_transmitted() << "\t" - << c.local().to_string() << "\t\t" << c.remote().to_string() << "\t\t" - << c.state().to_string() << "\n"; - } - return ss.str(); -} - -/*TCP::Socket& TCP::add_listener(TCP::Socket&& socket) { - -}*/ +string TCP::to_string() const { + // Write all connections in a cute list. + stringstream ss; + ss << "LISTENING SOCKETS:\n"; + for(auto listen_it : listeners_) { + ss << listen_it.second.to_string() << "\n"; + } + ss << "\nCONNECTIONS:\n" << "Proto\tRecv\tSend\tIn\tOut\tLocal\t\t\tRemote\t\t\tState\n"; + for(auto con_it : connections_) { + auto& c = *(con_it.second); + ss << "tcp4\t" + << " " << "\t" << " " << "\t" + << " " << "\t" << " " << "\t" + << c.local().to_string() << "\t\t" << c.remote().to_string() << "\t\t" + << c.state().to_string() << "\n"; + } + return ss.str(); +} + TCP::Connection_ptr TCP::add_connection(Port local_port, TCP::Socket remote) { - return (connections_.emplace( - Connection::Tuple{ local_port, remote }, - std::make_shared(*this, local_port, remote)) - ).first->second; + return (connections_.emplace( + Connection::Tuple{ local_port, remote }, + std::make_shared(*this, local_port, remote)) + ).first->second; } void TCP::close_connection(TCP::Connection& conn) { - debug(" Closing connection: %s \n", conn.to_string().c_str()); - connections_.erase(conn.tuple()); - debug2(" TCP Status: \n%s \n", status().c_str()); + debug(" Closing connection: %s \n", conn.to_string().c_str()); + connections_.erase(conn.tuple()); } void TCP::drop(TCP::Packet_ptr) { - //debug(" Packet was dropped - no recipient: %s \n", packet->destination().to_string().c_str()); + //debug(" Packet was dropped - no recipient: %s \n", packet->destination().to_string().c_str()); } void TCP::transmit(TCP::Packet_ptr packet) { - // Translate into a net::Packet_ptr and send away. - // Generate checksum. - packet->set_checksum(TCP::checksum(packet)); - //packet->set_checksum(checksum(packet)); - _network_layer_out(packet); + // Generate checksum. + packet->set_checksum(TCP::checksum(packet)); + //if(packet->has_data()) + // printf(" S: %u\n", packet->seq()); + _network_layer_out(packet); } diff --git a/src/net/tcp_connection.cpp b/src/net/tcp_connection.cpp index 32e56725bf..d702cf4ab9 100644 --- a/src/net/tcp_connection.cpp +++ b/src/net/tcp_connection.cpp @@ -6,9 +6,9 @@ // 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. @@ -25,341 +25,788 @@ using Connection = TCP::Connection; using namespace std; +const TCP::Connection::RTTM::duration_t TCP::Connection::RTTM::CLOCK_G; + /* - This is most likely used in a ACTIVE open + This is most likely used in a ACTIVE open */ Connection::Connection(TCP& host, Port local_port, Socket remote) : - host_(host), - local_port_(local_port), - remote_(remote), - state_(&Connection::Closed::instance()), - prev_state_(state_), - control_block(), - receive_buffer_(host.buffer_limit()), - send_buffer_(host.buffer_limit()), - time_wait_started(0) + host_(host), + local_port_(local_port), + remote_(remote), + state_(&Connection::Closed::instance()), + prev_state_(state_), + cb(), + read_request(), + writeq(), + queued_(false), + time_wait_started(0) { - + setup_congestion_control(); } /* - This is most likely used in a PASSIVE open + This is most likely used in a PASSIVE open */ -Connection::Connection(TCP& host, Port local_port) : - host_(host), - local_port_(local_port), - remote_(TCP::Socket()), - state_(&Connection::Closed::instance()), - prev_state_(state_), - control_block(), - receive_buffer_(host.buffer_limit()), - send_buffer_(host.buffer_limit()), - time_wait_started(0) +Connection::Connection(TCP& host, Port local_port) + : Connection(host, local_port, TCP::Socket()) { - -} - - -size_t Connection::read(char* buffer, size_t n) { - debug(" Reading %u bytes of data from RCV buffer. Total amount of packets stored: %u\n", - n, receive_buffer_.size()); - try { - return state_->receive(*this, buffer, n); - } catch(TCPException err) { - signal_error(err); - return 0; - } -} - -std::string Connection::read(size_t n) { - if(n == 0) { - // Read all data. - n = receive_buffer_.data_size(); - } - char buffer[n]; - size_t length = read(&buffer[0], n); - return {buffer, length}; -} - -size_t Connection::read_from_receive_buffer(char* buffer, size_t n) { - size_t bytes_read = 0; - // Read data to buffer until either whole buffer is emptied, or the user got all the data requested. - while(!receive_buffer_.empty() and bytes_read < n) - { - // Packet in front - auto packet = receive_buffer_.front(); - // Where to begin reading - char* begin = packet->data()+receive_buffer_.data_offset(); - // Read this iteration - size_t total{0}; - // Remaining bytes to read. - size_t remaining = n - bytes_read; - // Trying to read over more than one packet - if( remaining >= (packet->data_length() - receive_buffer_.data_offset()) ) { - debug2(" Remaining >: %u Current p: %u\n", - remaining, packet->data_length() - receive_buffer_.data_offset()); - // Reading whole packet - total = packet->data_length(); - // Removing packet from receive buffer. - receive_buffer_.pop(); - // Next packet will start from beginning. - receive_buffer_.set_data_offset(0); - } - // Reading less than one packet. - else { - debug2(" Remaining <: %u\n", remaining); - total = remaining; - receive_buffer_.set_data_offset(packet->data_length() - remaining); - } - memcpy(buffer+bytes_read, begin, total); - bytes_read += total; - } - - return bytes_read; -} - -bool Connection::add_to_receive_buffer(TCP::Packet_ptr packet) { - return receive_buffer_.add(packet); -} - -size_t Connection::write(const char* buffer, size_t n, bool PUSH) { - debug(" Asking to write %u bytes of data to SND buffer. \n", n); - try { - return state_->send(*this, buffer, n, PUSH); - } catch(TCPException err) { - signal_error(err); - return 0; - } -} - -size_t Connection::write_to_send_buffer(const char* buffer, size_t n, bool PUSH) { - size_t bytes_written{0}; - size_t remaining{n}; - do { - auto packet = create_outgoing_packet(); - size_t written = packet->set_seq(control_block.SND.NXT).set_ack(control_block.RCV.NXT).set_flag(ACK).fill(buffer + (n-remaining), remaining); - bytes_written += written; - remaining -= written; - - debug(" Written: %u - Remaining: %u \n", written, remaining); - - // If last packet, add PUSH. - if(!remaining and PUSH) - packet->set_flag(PSH); - - // Advance outgoing sequence number (SND.NXT) with the length of the data. - control_block.SND.NXT += packet->data_length(); - } while(remaining and !send_buffer_.full()); - - return bytes_written; + } -/* - If ACTIVE: - Need a remote Socket. -*/ -void Connection::open(bool active) { - try { - debug(" Trying to open Connection...\n"); - state_->open(*this, active); - } - // No remote host, or state isnt valid for opening. - catch (TCPException e) { - debug(" Cannot open Connection. \n"); - signal_error(e); - } +void Connection::read(ReadBuffer buffer, ReadCallback callback) { + try { + state_->receive(*this, buffer); + read_request.callback = callback; + } + catch (TCPException err) { + callback(buffer.buffer, buffer.size()); + } } -void Connection::close() { - debug(" Active close on connection. \n"); - try { - state_->close(*this); - if(is_state(Closed::instance())) - signal_close(); - } catch(TCPException err) { - signal_error(err); - } +size_t Connection::receive(const uint8_t* data, size_t n, bool PUSH) { + // should not be called without an read request + assert(read_request.buffer.capacity()); + assert(n); + auto& buf = read_request.buffer; + size_t received{0}; + while(n) { + auto read = receive(buf, data+received, n); + // nothing was read to buffer + if(!buf.advance(read)) { + // buffer should be full + assert(buf.full()); + // signal the user + read_request.callback(buf.buffer, buf.size()); + // renew the buffer, releasing the old one + buf.renew(); + } + n -= read; + received += read; + } + // n shouldnt be negative + assert(n == 0); + + // end of data, signal the user + if(PUSH) { + buf.push = PUSH; + read_request.callback(buf.buffer, buf.size()); + // renew the buffer, releasing the old one + buf.renew(); + } + + return received; } -string Connection::to_string() const { - ostringstream os; - os << local().to_string() << "\t" << remote_.to_string() << "\t" << state_->to_string(); - return os.str(); + +void Connection::write(WriteBuffer buffer, WriteCallback callback) { + try { + // try to write + auto written = state_->send(*this, buffer); + debug(" off=%u rem=%u written=%u\n", + buffer.offset, buffer.remaining, written); + // put request in line + writeq.push_back({buffer, callback}); + // if data was written, advance + if(written) { + writeq.advance(written); + } + } + catch(TCPException err) { + callback(0); + } +} + +void Connection::offer(size_t& packets) { + Expects(packets); + + debug(" %s got offered [%u] packets. Usable window is %u.\n", + to_string().c_str(), packets, usable_window()); + + // write until we either cant send more (window closes or no more in queue), + // or we're out of packets. + while(can_send() and packets) + { + auto packet = create_outgoing_packet(); + packets--; + + // get next request in writeq + auto& buf = writeq.nxt(); + // fill the packet with data + auto written = fill_packet(packet, (char*)buf.pos(), buf.remaining, cb.SND.NXT); + cb.SND.NXT += packet->data_length(); + + // advance the write q + writeq.advance(written); + + debug2(" Wrote %u bytes (%u remaining) with [%u] packets left and a usable window of %u.\n", + written, buf.remaining, packets, usable_window()); + + transmit(packet); + } + + debug(" Finished working offer with [%u] packets left and a queue of (%u) with a usable window of %i\n", + packets, writeq.size(), usable_window()); +} + +size_t Connection::send(const char* buffer, size_t remaining, size_t& packets_avail) { + + size_t bytes_written = 0; + debug(" Trying to send %u bytes. Starting with uw=%u packets=%u\n", + remaining, usable_window(), packets_avail); + + std::vector packets; + + while(remaining and usable_window() >= SMSS() and packets_avail) + { + auto packet = create_outgoing_packet(); + packets_avail--; + + auto written = fill_packet(packet, buffer+bytes_written, remaining, cb.SND.NXT); + cb.SND.NXT += packet->data_length(); + + bytes_written += written; + remaining -= written; + + if(!remaining or usable_window() < SMSS() or !packets_avail) + packet->set_flag(PSH); + + transmit(packet); + //packets.push_back(packet); + } + /*Ensures(!packets.empty()); + // get first packet + auto i = packets.begin(); + auto head = i++; + // chain packets + while(i != packets.end()) { + (*head)->chain(*i); + head = i++; + } + // set push + (*head)->set_flag(PSH); + + // transmit first packet + transmit(packets.front());*/ + + debug(" Sent %u bytes. Finished with uw=%u packets=%u\n", + bytes_written, usable_window(), packets_avail); + + return bytes_written; } /* - Where the magic happens. -*/ -void Connection::receive(TCP::Packet_ptr incoming) { - // Let state handle what to do when incoming packet arrives, and modify the outgoing packet. - signal_packet_received(incoming); - // Change window accordingly. - control_block.SND.WND = incoming->win(); - switch(state_->handle(*this, incoming)) { - case State::OK: { - // Do nothing. - break; - } - case State::CLOSED: { - debug(" State handle finished with CLOSED. We're done, ask host() to delete the connection. \n"); - signal_close(); - break; - }; - case State::CLOSE: { - debug(" State handle finished with CLOSE. onDisconnect has been called, close the connection. \n"); - state_->close(*this); - break; - }; - } +void Connection::set_window(Packet_ptr packet) { + packet->set_win(cb.RCV.WND); } -bool Connection::is_listening() const { - return is_state(Listen::instance()); +void Connection::make_flight_ready(Packet_ptr packet) { + set_window(packet); + + // Set Source (local == the current connection) + packet->set_source(local()); + // Set Destination (remote) + packet->set_destination(remote_); + + // set correct sequence numbers + packet->set_seq(cb.SND.NXT).set_ack(cb.RCV.NXT); +}*/ + +void Connection::writeq_push() { + while(writeq.remaining_requests()) { + auto& buf = writeq.nxt(); + auto written = host_.send(shared_from_this(), (char*)buf.pos(), buf.remaining); + writeq.advance(written); + if(buf.remaining) + return; + } } -bool Connection::is_connected() const { - return is_state(Established::instance()); +size_t Connection::fill_packet(Packet_ptr packet, const char* buffer, size_t n, Seq seq) { + Expects(!packet->has_data()); + + auto written = packet->fill(buffer, std::min(n, (size_t)SMSS())); + + packet->set_seq(seq).set_ack(cb.RCV.NXT).set_flag(ACK); + + Ensures(written <= n); + + return written; } -bool Connection::is_closing() const { - return (is_state(Closing::instance()) or is_state(LastAck::instance()) or is_state(TimeWait::instance())); +void Connection::limited_tx() { + + auto packet = create_outgoing_packet(); + + debug(" UW: %u CW: %u, FS: %u\n", usable_window(), cb.cwnd, flight_size()); + + auto& buf = writeq.nxt(); + + auto written = fill_packet(packet, (char*)buf.pos(), buf.remaining, cb.SND.NXT); + cb.SND.NXT += packet->data_length(); + + writeq.advance(written); + + transmit(packet); +} + +void Connection::writeq_reset() { + debug2(" Reseting.\n"); + writeq.reset(); } -bool Connection::is_writable() const { - return (is_connected() and (!send_buffer_.full())); +void Connection::open(bool active) { + try { + debug(" Trying to open Connection...\n"); + state_->open(*this, active); + } + // No remote host, or state isnt valid for opening. + catch (TCPException e) { + debug(" Cannot open Connection. \n"); + signal_error(e); + } +} + +void Connection::close() { + debug(" Active close on connection. \n"); + try { + state_->close(*this); + if(is_state(Closed::instance())) + signal_close(); + } catch(TCPException err) { + signal_error(err); + } +} + +/* + Local:Port Remote:Port (STATE) +*/ +string Connection::to_string() const { + ostringstream os; + os << local().to_string() << " " << remote_.to_string() << " (" << state_->to_string() << ")"; + return os.str(); +} + +void Connection::segment_arrived(TCP::Packet_ptr incoming) { + + signal_packet_received(incoming); + + if(incoming->has_options()) { + try { + parse_options(incoming); + } + catch(TCPBadOptionException err) { + debug(" %s \n", err.what()); + } + } + + // Let state handle what to do when incoming packet arrives, and modify the outgoing packet. + switch(state_->handle(*this, incoming)) { + case State::OK: { + // Do nothing. + break; + } + case State::CLOSED: { + debug(" (%s => %s) State handle finished with CLOSED. We're done, ask host() to delete the connection.\n", + prev_state_->to_string().c_str(), state_->to_string().c_str()); + writeq_reset(); + signal_close(); + break; + }; + case State::CLOSE: { + debug(" State handle finished with CLOSE. onDisconnect has been called, close the connection. \n"); + state_->close(*this); + break; + }; + } +} + +bool Connection::is_listening() const { + return is_state(Listen::instance()); } Connection::~Connection() { - // Do all necessary clean up. - // Free up buffers etc. - debug2(" Bye bye... \n"); + // Do all necessary clean up. + // Free up buffers etc. + debug2(" Bye bye... \n"); } TCP::Packet_ptr Connection::create_outgoing_packet() { - auto packet = std::static_pointer_cast((host_.inet_).createPacket(TCP::Packet::HEADERS_SIZE)); - - packet->init(); - // Set Source (local == the current connection) - packet->set_source(local()); - // Set Destination (remote) - packet->set_destination(remote_); - - packet->set_win_size(control_block.SND.WND); - - // Set SEQ and ACK - I think this is OK.. - packet->set_seq(control_block.SND.NXT).set_ack(control_block.RCV.NXT); - debug(" Outgoing packet created: %s \n", packet->to_string().c_str()); - - // Will also add the packet to the back of the send queue. - send_buffer_.push(packet); - - return packet; -} - -void Connection::transmit() { - assert(! send_buffer_.empty() ); - - debug(" Transmitting: [ %i ] packets. \n", send_buffer_.size()); - while(! send_buffer_.empty() ) { - auto packet = send_buffer_.front(); - assert(! packet->destination().is_empty()); - transmit(packet); - send_buffer_.pop(); - } + auto packet = std::static_pointer_cast((host_.inet_).createPacket(TCP::Packet::HEADERS_SIZE)); + //auto packet = host_.create_empty_packet(); + + packet->init(); + // Set Source (local == the current connection) + packet->set_source(local()); + // Set Destination (remote) + packet->set_destination(remote_); + + packet->set_win(cb.RCV.WND); + + // Set SEQ and ACK - I think this is OK.. + packet->set_seq(cb.SND.NXT).set_ack(cb.RCV.NXT); + debug(" Outgoing packet created: %s \n", packet->to_string().c_str()); + + return packet; } void Connection::transmit(TCP::Packet_ptr packet) { - debug(" Transmitting: %s \n", packet->to_string().c_str()); - host_.transmit(packet); - // Don't think we would like to retransmit reset packets..? - //if(!packet->isset(RST)) - // add_retransmission(packet); + if(!rttm.active and packet->end() == cb.SND.NXT) { + //printf(" Starting RTT measurement.\n"); + rttm.start(); + } + //if(packet->seq() + packet->data_length() != cb.SND.NXT) + //printf(" rseq=%u rack=%u\n", + // packet->seq() - cb.ISS, packet->ack() - cb.IRS); + debug2(" TX %s\n", packet->to_string().c_str()); + + host_.transmit(packet); + if(packet->has_data() and !rtx_timer.active) { + rtx_start(); + } } -TCP::Packet_ptr Connection::outgoing_packet() { - if(send_buffer_.empty()) - create_outgoing_packet(); - return send_buffer_.back(); +bool Connection::can_send_one() { + return send_window() >= SMSS() and writeq.remaining_requests(); } -TCP::Seq Connection::generate_iss() { - return host_.generate_iss(); +bool Connection::can_send() { + return (usable_window() >= SMSS()) and writeq.remaining_requests(); } -void Connection::set_state(State& state) { - prev_state_ = state_; - state_ = &state; - debug(" %s => %s \n", - prev_state_->to_string().c_str(), state_->to_string().c_str()); -} - -void Connection::add_retransmission(TCP::Packet_ptr packet) { - debug2(" Packet added to retransmission. \n"); - auto self = shared_from_this(); - hw::PIT::instance().onTimeout(RTO(), [packet, self] { - // Packet hasnt been ACKed. - if(packet->seq() > self->tcb().SND.UNA) { - debug(" Packet unacknowledge, retransmitting...\n"); - self->transmit(packet); - } else { - debug2(" Packet acknowledged %s \n", packet->to_string().c_str()); - // Signal user? - } - }); +void Connection::send_much() { + writeq_push(); +} + +bool Connection::handle_ack(TCP::Packet_ptr in) { + // dup ack + /* + 1. Same ACK as latest received + 2. outstanding data + 3. packet is empty + 4. is not an wnd update + */ + if(in->ack() == cb.SND.UNA and flight_size() + and !in->has_data() and cb.SND.WND == in->win() + and !in->isset(SYN) and !in->isset(FIN)) + { + dup_acks_++; + on_dup_ack(); + return false; + } // < dup ack + + // new ack + else if(in->ack() >= cb.SND.UNA) { + + if( cb.SND.WL1 < in->seq() or ( cb.SND.WL1 == in->seq() and cb.SND.WL2 <= in->ack() ) ) + { + cb.SND.WND = in->win(); + cb.SND.WL1 = in->seq(); + cb.SND.WL2 = in->ack(); + //printf(" Window update (%u)\n", cb.SND.WND); + } + + acks_rcvd_++; + + debug(" New ACK#%u: %u FS: %u %s\n", acks_rcvd_, + in->ack() - cb.ISS, flight_size(), fast_recovery ? "[RECOVERY]" : ""); + + // [RFC 6582] p. 8 + prev_highest_ack_ = cb.SND.UNA; + highest_ack_ = in->ack(); + + // used for cwnd calculation (Reno) + size_t bytes_acked = in->ack() - cb.SND.UNA; + cb.SND.UNA = in->ack(); + + // ack everything in write queue + if(!writeq.empty()) + rtx_ack(in->ack()); + + // update cwnd when congestion avoidance? + bool cong_avoid_rtt = false; + + // if measuring round trip time, stop + if(rttm.active) { + rttm.stop(); + cong_avoid_rtt = true; + } + + // no fast recovery + if(!fast_recovery) { + //printf(" Not in Recovery\n"); + dup_acks_ = 0; + cb.recover = cb.SND.NXT; + + // slow start + if(cb.slow_start()) { + reno_increase_cwnd(bytes_acked); + debug2(" Slow start. cwnd=%u uw=%u\n", + cb.cwnd, usable_window()); + } + + // congestion avoidance + else { + // increase cwnd once per RTT + cb.cwnd += std::max(SMSS()*SMSS()/cb.cwnd, (uint32_t)1); + debug2(" Congestion avoidance. cwnd=%u uw=%u\n", + cb.cwnd, usable_window()); + } // < congestion avoidance + + // try to write + //if(can_send() and acks_rcvd_ % 2 == 1) + if(can_send()) + send_much(); + + // if data, let state continue process + if(in->has_data() or in->isset(FIN)) + return true; + + } // < !fast recovery + + // we're in fast recovery + else { + //printf(" In Recovery\n"); + // partial ack + if(!reno_full_ack(in->ack())) { + debug(" Partial ACK\n"); + reno_deflate_cwnd(bytes_acked); + //printf(" Recovery - Partial ACK\n"); + retransmit(); + + //dup_acks_ = 0; + + if(!reno_fpack_seen) { + rtx_reset(); + reno_fpack_seen = true; + } + + // send one segment if possible + if(can_send()) { + debug(" Sending one packet during recovery.\n"); + limited_tx(); + } else { + debug(" Can't send during recovery - usable window is closed.\n"); + } + + if(in->has_data() or in->isset(FIN)) + return true; + } // < partial ack + + // full ack + else { + debug(" Full ACK.\n"); + dup_acks_ = 0; + finish_fast_recovery(); + } // < full ack + + } // < fast recovery + + } // < new ack + + // ACK outside + else { + return true; + } + return false; +} + +/* + Reno [RFC 5681] p. 9 + + What to do when received ACK is a duplicate. +*/ +void Connection::on_dup_ack() { + debug2(" rack=%u i=%u\n", cb.SND.UNA - cb.ISS, dup_acks_); + // if less than 3 dup acks + if(dup_acks_ < 3) { + + if(limited_tx_) { + // try to send one segment + if(cb.SND.WND >= SMSS() and (flight_size() <= cb.cwnd + 2*SMSS()) and writeq.remaining_requests()) { + limited_tx(); + } + } + } + + // 3 dup acks + else if(dup_acks_ == 3) { + debug(" Dup ACK == 3 - %u\n", cb.SND.UNA); + + if(cb.SND.UNA - 1 > cb.recover + or ( congestion_window() > SMSS() and (highest_ack_ - prev_highest_ack_ <= 4*SMSS()) )) + { + cb.recover = cb.SND.NXT; + debug(" Enter Recovery - Flight Size: %u\n", flight_size()); + fast_retransmit(); + } + } + + // > 3 dup acks + else { + cb.cwnd += SMSS(); + // send one segment if possible + if(can_send()) + limited_tx(); + } +} + +/* + [RFC 6298] + + (5.2) When all outstanding data has been acknowledged, turn off the + retransmission timer. + + (5.3) When an ACK is received that acknowledges new data, restart the + retransmission timer so that it will expire after RTO seconds + (for the current value of RTO). +*/ +void Connection::rtx_ack(const Seq ack) { + auto acked = ack - prev_highest_ack_; + writeq.acknowledge(acked); + /* + When all outstanding data has been acknowledged, turn off the + retransmission timer. + */ + if(cb.SND.UNA == cb.SND.NXT) { + rtx_stop(); + rto_attempt = 0; + } + /* + When an ACK is received that acknowledges new data, restart the + retransmission timer so that it will expire after RTO seconds + (for the current value of RTO). + */ + else if(acked > 0) { + rtx_reset(); + rto_attempt = 0; + } + + //printf(" ACK'ed %u packets. rtx_q: %u\n", + // x-rtx_q.size(), rtx_q.size()); +} + + +void Connection::retransmit() { + auto packet = create_outgoing_packet(); + auto& buf = writeq.una(); + fill_packet(packet, (char*)buf.pos(), buf.remaining, cb.SND.UNA); + packet->set_flag(ACK); + //printf(" rseq=%u \n", packet->seq() - cb.ISS); + debug(" RT %s\n", packet->to_string().c_str()); + host_.transmit(packet); + /* + Every time a packet containing data is sent (including a + retransmission), if the timer is not running, start it running + so that it will expire after RTO seconds (for the current value + of RTO). + */ + if(packet->has_data() and !rtx_timer.active) { + rtx_start(); + } +} + +void Connection::rtx_start() { + Expects(!rtx_timer.active); + auto i = rtx_timer.i; + auto rto = rttm.RTO; + rtx_timer.iter = hw::PIT::instance().on_timeout(rttm.RTO, + [this, i, rto] + { + rtx_timer.active = false; + debug(" %s Timed out (%f). FS: %u, i: %u rt_i: %u\n", + to_string().c_str(), rto, flight_size(), i, rtx_timer.i); + rtx_timeout(); + }); + rtx_timer.i++; + rtx_timer.active = true; } + +void Connection::rtx_stop() { + Expects(rtx_timer.active); + hw::PIT::instance().stop_timer(rtx_timer.iter); + rtx_timer.active = false; +} + +void Connection::rtx_clear() { + if(rtx_timer.active) + rtx_stop(); +} + /* - Next compute a Smoothed Round Trip Time (SRTT) as: + When the retransmission timer expires, do the following: - SRTT = ( ALPHA * SRTT ) + ((1-ALPHA) * RTT) + (5.4) Retransmit the earliest segment that has not been acknowledged + by the TCP receiver. - and based on this, compute the retransmission timeout (RTO) as: + (5.5) The host MUST set RTO <- RTO * 2 ("back off the timer"). The + maximum value discussed in (2.5) above may be used to provide + an upper bound to this doubling operation. - RTO = min[UBOUND,max[LBOUND,(BETA*SRTT)]] + (5.6) Start the retransmission timer, such that it expires after RTO + seconds (for the value of RTO after the doubling operation + outlined in 5.5). - where UBOUND is an upper bound on the timeout (e.g., 1 minute), - LBOUND is a lower bound on the timeout (e.g., 1 second), ALPHA is - a smoothing factor (e.g., .8 to .9), and BETA is a delay variance - factor (e.g., 1.3 to 2.0). + (5.7) If the timer expires awaiting the ACK of a SYN segment and the + TCP implementation is using an RTO less than 3 seconds, the RTO + MUST be re-initialized to 3 seconds when data transmission + begins (i.e., after the three-way handshake completes). */ -std::chrono::milliseconds Connection::RTO() const { - return 1s; +void Connection::rtx_timeout() { + // retransmit SND.UNA + retransmit(); + + if(cb.SND.UNA != cb.ISS) { + // "back off" timer + rttm.RTO *= 2.0; + } + // we never queue SYN packets since they don't carry data.. + else { + rttm.RTO = 3.0; + } + // timer need to be restarted + if(!rtx_timer.active) + rtx_start(); + + /* + [RFC 5681] p. 7 + + When a TCP sender detects segment loss using the retransmission timer + and the given segment has not yet been resent by way of the + retransmission timer, the value of ssthresh MUST be set to no more + than the value given in equation (4): + + ssthresh = max (FlightSize / 2, 2*SMSS) + */ + if(rto_attempt++ == 0) + reduce_ssthresh(); + + /* + [RFC 6582] p. 6 + After a retransmit timeout, record the highest sequence number + transmitted in the variable recover, and exit the fast recovery + procedure if applicable. + */ + + // update recover + cb.recover = cb.SND.NXT; + + if(fast_recovery) // not sure if this is correct + finish_fast_recovery(); + + cb.cwnd = SMSS(); + + /* + NOTE: It's unclear which one comes first, or if finish_fast_recovery includes changing the cwnd. + */ +} + +TCP::Seq Connection::generate_iss() { + return host_.generate_iss(); +} + +void Connection::set_state(State& state) { + prev_state_ = state_; + state_ = &state; + debug(" %s => %s \n", + prev_state_->to_string().c_str(), state_->to_string().c_str()); } + void Connection::start_time_wait_timeout() { - debug2(" Time Wait timer started. \n"); - time_wait_started = OS::cycles_since_boot(); - auto timeout = 2 * host().MSL(); // 60 seconds - // Passing "this"..? - hw::PIT::instance().onTimeout(timeout,[this, timeout] { - // The timer hasnt been updated - if( OS::cycles_since_boot() >= (time_wait_started + timeout.count()) ) { - signal_close(); - } else { - debug2(" time_wait_started has been updated. \n"); - } - }); + debug2(" Time Wait timer started. \n"); + time_wait_started = OS::cycles_since_boot(); + auto timeout = 2 * host().MSL(); // 60 seconds + // Passing "this"..? + hw::PIT::instance().onTimeout(timeout,[this, timeout] { + // The timer hasnt been updated + if( OS::cycles_since_boot() >= (time_wait_started + timeout.count()) ) { + signal_close(); + } else { + debug2(" time_wait_started has been updated. \n"); + } + }); } void Connection::signal_close() { - debug(" It's time to delete this connection. \n"); - host_.close_connection(*this); + debug(" It's time to delete this connection. \n"); + host_.close_connection(*this); } std::string Connection::TCB::to_string() const { - ostringstream os; - os << "SND" - << " .UNA = " << SND.UNA - << " .NXT = " << SND.NXT - << " .WND = " << SND.WND - << " .UP = " << SND.UP - << " .WL1 = " << SND.WL1 - << " .WL2 = " << SND.WL2 - << " ISS = " << ISS - << "\n RCV" - << " .NXT = " << RCV.NXT - << " .WND = " << RCV.WND - << " .UP = " << RCV.UP - << " IRS = " << IRS; - return os.str(); + ostringstream os; + os << "SND" + << " .UNA = " << SND.UNA + << " .NXT = " << SND.NXT + << " .WND = " << SND.WND + << " .UP = " << SND.UP + << " .WL1 = " << SND.WL1 + << " .WL2 = " << SND.WL2 + << " ISS = " << ISS + << "\n RCV" + << " .NXT = " << RCV.NXT + << " .WND = " << RCV.WND + << " .UP = " << RCV.UP + << " IRS = " << IRS; + return os.str(); +} + +void Connection::parse_options(TCP::Packet_ptr packet) { + assert(packet->has_options()); + debug(" Parsing options. Offset: %u, Options: %u \n", + packet->offset(), packet->options_length()); + + auto* opt = packet->options(); + + while((char*)opt < packet->data()) { + + auto* option = (TCP::Option*)opt; + + switch(option->kind) { + + case Option::END: { + return; + } + + case Option::NOP: { + opt++; + break; + } + + case Option::MSS: { + // unlikely + if(option->length != 4) + throw TCPBadOptionException{Option::MSS, "length != 4"}; + // unlikely + if(!packet->isset(SYN)) + throw TCPBadOptionException{Option::MSS, "Non-SYN packet"}; + + auto* opt_mss = (Option::opt_mss*)option; + uint16_t mss = ntohs(opt_mss->mss); + cb.SND.MSS = mss; + debug2(" MSS: %u \n", mss); + opt += option->length; + break; + } + + default: + return; + } + } +} + +void Connection::add_option(TCP::Option::Kind kind, TCP::Packet_ptr packet) { + + switch(kind) { + + case Option::MSS: { + packet->add_option(host_.MSS()); + debug2(" Packet: %s - MSS: %u\n", + packet->to_string().c_str(), ntohs(*(uint16_t*)(packet->options()+2))); + break; + } + default: + break; + } } diff --git a/src/net/tcp_connection_states.cpp b/src/net/tcp_connection_states.cpp index 355a1406dd..f4ca0b9cc9 100644 --- a/src/net/tcp_connection_states.cpp +++ b/src/net/tcp_connection_states.cpp @@ -6,9 +6,9 @@ // 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. @@ -16,1176 +16,1332 @@ // limitations under the License. #include +#include using namespace std; - ///////////////////////////////////////////////////////////////////// /* - COMMON STATE FUNCTIONS + COMMON STATE FUNCTIONS + + These functions are helper functions and used by more than one state. */ ///////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////// /* - 1. Check Sequence -*/ -/* - SYN-RECEIVED STATE - ESTABLISHED STATE - FIN-WAIT-1 STATE - FIN-WAIT-2 STATE - CLOSE-WAIT STATE - CLOSING STATE - LAST-ACK STATE - TIME-WAIT STATE + 1. Check Sequence number. + + Used for checking if the sequence number is acceptable. + + [RFC 793]: + + SYN-RECEIVED STATE + ESTABLISHED STATE + FIN-WAIT-1 STATE + FIN-WAIT-2 STATE + CLOSE-WAIT STATE + CLOSING STATE + LAST-ACK STATE + TIME-WAIT STATE - Segments are processed in sequence. Initial tests on arrival - are used to discard old duplicates, but further processing is - done in SEG.SEQ order. If a segment's contents straddle the - boundary between old and new, only the new parts should be - processed. + Segments are processed in sequence. Initial tests on arrival + are used to discard old duplicates, but further processing is + done in SEG.SEQ order. If a segment's contents straddle the + boundary between old and new, only the new parts should be + processed. - There are four cases for the acceptability test for an incoming - segment: + There are four cases for the acceptability test for an incoming + segment: - Segment Receive Test - Length Window - ------- ------- ------------------------------------------- + Segment Receive Test + Length Window + ------- ------- ------------------------------------------- - 0 0 SEG.SEQ = RCV.NXT + 0 0 SEG.SEQ = RCV.NXT - 0 >0 RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND + 0 >0 RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND - >0 0 not acceptable + >0 0 not acceptable - >0 >0 RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND - or RCV.NXT =< SEG.SEQ+SEG.LEN-1 < RCV.NXT+RCV.WND + >0 >0 RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND + or RCV.NXT =< SEG.SEQ+SEG.LEN-1 < RCV.NXT+RCV.WND - If the RCV.WND is zero, no segments will be acceptable, but - special allowance should be made to accept valid ACKs, URGs and - RSTs. + If the RCV.WND is zero, no segments will be acceptable, but + special allowance should be made to accept valid ACKs, URGs and + RSTs. + If an incoming segment is not acceptable, an acknowledgment + should be sent in reply (unless the RST bit is set, if so drop + the segment and return): + + + + After sending the acknowledgment, drop the unacceptable segment + and return. + + In the following it is assumed that the segment is the idealized + segment that begins at RCV.NXT and does not exceed the window. + One could tailor actual segments to fit this assumption by + trimming off any portions that lie outside the window (including + SYN and FIN), and only processing further if the segment then + begins at RCV.NXT. Segments with higher begining sequence + numbers may be held for later processing. +*/ +///////////////////////////////////////////////////////////////////// + +// TODO: Optimize this one. It checks for the same things. +bool Connection::State::check_seq(Connection& tcp, TCP::Packet_ptr in) { + auto& tcb = tcp.tcb(); + bool acceptable = false; + debug2(" TCB: %s \n",tcb.to_string().c_str()); + // #1 + if( in->seq() == tcb.RCV.NXT ) { + acceptable = true; + } + // #2 + else if( tcb.RCV.NXT <= in->seq() and in->seq() < tcb.RCV.NXT + tcb.RCV.WND ) { + acceptable = true; + } + // #3 (INVALID) + else if( in->seq() + in->data_length()-1 > tcb.RCV.NXT+tcb.RCV.WND ) { + acceptable = false; + } + // #4 + else if( (tcb.RCV.NXT <= in->seq() and in->seq() < tcb.RCV.NXT + tcb.RCV.WND) + or ( tcb.RCV.NXT <= in->seq()+in->data_length()-1 and in->seq()+in->data_length()-1 < tcb.RCV.NXT+tcb.RCV.WND ) ) { + acceptable = true; + } + /* If an incoming segment is not acceptable, an acknowledgment should be sent in reply (unless the RST bit is set, if so drop the segment and return): - + After sending the acknowledgment, drop the unacceptable segment and return. - - In the following it is assumed that the segment is the idealized - segment that begins at RCV.NXT and does not exceed the window. - One could tailor actual segments to fit this assumption by - trimming off any portions that lie outside the window (including - SYN and FIN), and only processing further if the segment then - begins at RCV.NXT. Segments with higher begining sequence - numbers may be held for later processing. -*/ - -/* - TODO: Optimize this one. It checks for the same things. -*/ -bool Connection::State::check_seq(Connection& tcp, TCP::Packet_ptr in) { - auto& tcb = tcp.tcb(); - bool acceptable = false; - debug2(" TCB: %s \n",tcb.to_string().c_str()); - // #1 - if( in->seq() == tcb.RCV.NXT ) { - acceptable = true; - } - // #2 - else if( tcb.RCV.NXT <= in->seq() and in->seq() < tcb.RCV.NXT + tcb.RCV.WND ) { - acceptable = true; - } - // #3 (INVALID) - else if( in->seq() + in->data_length()-1 > tcb.RCV.NXT+tcb.RCV.WND ) { - acceptable = false; - } - // #4 - else if( (tcb.RCV.NXT <= in->seq() and in->seq() < tcb.RCV.NXT + tcb.RCV.WND) - or ( tcb.RCV.NXT <= in->seq()+in->data_length()-1 and in->seq()+in->data_length()-1 < tcb.RCV.NXT+tcb.RCV.WND ) ) { - acceptable = true; - } - /* - If an incoming segment is not acceptable, an acknowledgment - should be sent in reply (unless the RST bit is set, if so drop - the segment and return): - - - - After sending the acknowledgment, drop the unacceptable segment - and return. - */ - if(!acceptable) { - if(!in->isset(RST)) { - tcp.outgoing_packet()->set_seq(tcb.SND.NXT).set_ack(tcb.RCV.NXT).set_flag(ACK); - tcp.transmit(); - } - tcp.drop(in, "Unacceptable SEQ."); - return false; - } - debug2(" Acceptable SEQ: %u \n", in->seq()); - // is acceptable. - return true; + */ + if(!acceptable) { + if(!in->isset(RST)) { + auto packet = tcp.outgoing_packet(); + packet->set_seq(tcb.SND.NXT).set_ack(tcb.RCV.NXT).set_flag(ACK); + tcp.transmit(packet); + } + std::stringstream ss; + ss << "Unacceptable SEQ: " + << "[Packet: SEQ: " << in->seq() << " LEN: " << in->data_length() << "] " + << "[TCB: RCV.NXT: " << tcb.RCV.NXT << " RCV.WND: " << tcb.RCV.WND << "]"; + + tcp.drop(in, ss.str()); + return false; + } + debug2(" Acceptable SEQ: %u \n", in->seq()); + // is acceptable. + return true; } +///////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////// /* - 4. Check SYN + 4. Check SYN + + Used to filter out packets carrying SYN-flag when they're not supposed to. + + [RFC 793]: + + SYN-RECEIVED + ESTABLISHED STATE + FIN-WAIT STATE-1 + FIN-WAIT STATE-2 + CLOSE-WAIT STATE + CLOSING STATE + LAST-ACK STATE + TIME-WAIT STATE + + If the SYN is in the window it is an error, send a reset, any + outstanding RECEIVEs and SEND should receive "reset" responses, + all segment queues should be flushed, the user should also + receive an unsolicited general "connection reset" signal, enter + the CLOSED state, delete the TCB, and return. + + If the SYN is not in the window this step would not be reached + and an ack would have been sent in the first step (sequence + number check). */ +///////////////////////////////////////////////////////////////////// -/* - SYN-RECEIVED - ESTABLISHED STATE - FIN-WAIT STATE-1 - FIN-WAIT STATE-2 - CLOSE-WAIT STATE - CLOSING STATE - LAST-ACK STATE - TIME-WAIT STATE - - If the SYN is in the window it is an error, send a reset, any - outstanding RECEIVEs and SEND should receive "reset" responses, - all segment queues should be flushed, the user should also - receive an unsolicited general "connection reset" signal, enter - the CLOSED state, delete the TCB, and return. - - If the SYN is not in the window this step would not be reached - and an ack would have been sent in the first step (sequence - number check). -*/ void Connection::State::unallowed_syn_reset_connection(Connection& tcp, TCP::Packet_ptr in) { - assert(in->isset(SYN)); - debug(" Unallowed SYN for STATE: %s, reseting connection.\n", - tcp.state().to_string().c_str()); - // Not sure if this is the correct way to send a "reset response" - tcp.outgoing_packet()->set_seq(in->ack()).set_flag(RST); - tcp.transmit(); - tcp.signal_disconnect("Connection reset."); + assert(in->isset(SYN)); + debug(" Unallowed SYN for STATE: %s, reseting connection.\n", + tcp.state().to_string().c_str()); + // Not sure if this is the correct way to send a "reset response" + auto packet = tcp.outgoing_packet(); + packet->set_seq(in->ack()).set_flag(RST); + tcp.transmit(packet); + tcp.signal_disconnect(Disconnect::RESET); } +///////////////////////////////////////////////////////////////////// + +///////////////////////////////////////////////////////////////////// /* - 5. Check ACK + 5. Check ACK + + "Process" the packet if ACK is present. If not, drop the packet. + + [RFC 793] Page 72-73. */ +///////////////////////////////////////////////////////////////////// + bool Connection::State::check_ack(Connection& tcp, TCP::Packet_ptr in) { - // 5. ACK bit - debug2(" Checking for ACK in STATE: %s \n", tcp.state().to_string().c_str()); - if( in->isset(ACK) ) { - auto& tcb = tcp.tcb(); - /* - If SND.UNA < SEG.ACK =< SND.NXT then, set SND.UNA <- SEG.ACK. - Any segments on the retransmission queue which are thereby - entirely acknowledged are removed. Users should receive - positive acknowledgments for buffers which have been SENT and - fully acknowledged (i.e., SEND buffer should be returned with - "ok" response). If the ACK is a duplicate - (SEG.ACK < SND.UNA), it can be ignored. If the ACK acks - something not yet sent (SEG.ACK > SND.NXT) then send an ACK, - drop the segment, and return. - */ - if( tcb.SND.UNA < in->ack() and in->ack() <= tcb.SND.NXT ) { - tcb.SND.UNA = in->ack(); - // tcp.signal_sent(); - // return that buffer has been SENT - currently no support to receipt sent buffer. - - /* - If SND.UNA < SEG.ACK =< SND.NXT, the send window should be - updated. If (SND.WL1 < SEG.SEQ or (SND.WL1 = SEG.SEQ and - SND.WL2 =< SEG.ACK)), set SND.WND <- SEG.WND, set - SND.WL1 <- SEG.SEQ, and set SND.WL2 <- SEG.ACK. - */ - if( tcb.SND.WL1 < in->seq() or ( tcb.SND.WL1 = in->seq() and tcb.SND.WL2 <= in->ack() ) ) { - tcb.SND.WND = in->win(); - tcb.SND.WL1 = in->seq(); - tcb.SND.WL2 = in->ack(); - } - /* - Note that SND.WND is an offset from SND.UNA, that SND.WL1 - records the sequence number of the last segment used to update - SND.WND, and that SND.WL2 records the acknowledgment number of - the last segment used to update SND.WND. The check here - prevents using old segments to update the window. - */ - - } - /* If the ACK acks something not yet sent (SEG.ACK > SND.NXT) then send an ACK, drop the segment, and return. */ - else if( in->ack() > tcb.SND.NXT ) { - tcp.outgoing_packet()->set_flag(ACK); - tcp.transmit(); - tcp.drop(in, "ACK > SND.NXT"); - return false; - } - /* If the ACK is a duplicate (SEG.ACK < SND.UNA), it can be ignored. */ - /*else if( in->ack() < tcb.SND.UNA ) { - // ignore. - }*/ - return true; + debug2(" Checking for ACK in STATE: %s \n", tcp.state().to_string().c_str()); + if( in->isset(ACK) ) { + auto& tcb = tcp.tcb(); + /* + If SND.UNA < SEG.ACK =< SND.NXT then, set SND.UNA <- SEG.ACK. + Any segments on the retransmission queue which are thereby + entirely acknowledged are removed. Users should receive + positive acknowledgments for buffers which have been SENT and + fully acknowledged (i.e., SEND buffer should be returned with + "ok" response). If the ACK is a duplicate + (SEG.ACK < SND.UNA), it can be ignored. If the ACK acks + something not yet sent (SEG.ACK > SND.NXT) then send an ACK, + drop the segment, and return. + */ + // Correction: [RFC 1122 p. 94] + // ACK is inside sequence space + if(in->ack() <= tcb.SND.NXT ) { + + return tcp.handle_ack(in); + // this is a "new" ACK + //if(tcb.SND.UNA <= in->ack()) { + + // this is a NEW ACK + //if(tcb.SND.UNA < in->ack()) + //{ + // tcp.acknowledge(in->ack()); + //} + // [RFC 5681] + /* + DUPLICATE ACKNOWLEDGMENT: + An acknowledgment is considered a + "duplicate" in the following algorithms when + (a) the receiver of the ACK has outstanding data + (b) the incoming acknowledgment carries no data + (c) the SYN and FIN bits are both off + (d) the acknowledgment number is equal to the greatest acknowledgment + received on the given connection (TCP.UNA from [RFC793]) and + (e) the advertised window in the incoming acknowledgment equals the + advertised window in the last incoming acknowledgment. + + Note that a sender using SACK [RFC2018] MUST NOT send + new data unless the incoming duplicate acknowledgment contains + new SACK information. + */ + // this is a RFC 5681 DUP ACK + //!in->isset(FIN) and !in->isset(SYN) + //else if(tcp.reno_is_dup_ack(in)) { + // debug2(" Reno Dup ACK %u\n", in->ack()); + // tcp.reno_dup_ack(in->ack()); + //} + // this is an RFC 793 DUP ACK + //else { + //printf(" RFC 793 Dup ACK %u\n", in->ack()); + //} + //} + // this is an "old" ACK out of order + //else { + // printf(" ACK out of order (SND.UNA > ACK)\n"); + //} + // tcp.signal_sent(); + // return that buffer has been SENT - currently no support to receipt sent buffer. } - // ACK not set. + /* If the ACK acks something not yet sent (SEG.ACK > SND.NXT) then send an ACK, drop the segment, and return. */ else { - tcp.drop(in, "!ACK"); - return false; + auto packet = tcp.outgoing_packet(); + packet->set_flag(ACK); + tcp.transmit(packet); + tcp.drop(in, "ACK > SND.NXT"); + return false; } + return true; + } + // ACK not set. + else { + tcp.drop(in, "!ACK"); + return false; + } } - -/* - 7. Process the segment text -*/ +///////////////////////////////////////////////////////////////////// /* - Once in the ESTABLISHED state, it is possible to deliver segment - text to user RECEIVE buffers. Text from segments can be moved - into buffers until either the buffer is full or the segment is - empty. If the segment empties and carries an PUSH flag, then - the user is informed, when the buffer is returned, that a PUSH - has been received. + 7. Process the segment text - When the TCP takes responsibility for delivering the data to the - user it must also acknowledge the receipt of the data. + If a packet has data, process the data. - Once the TCP takes responsibility for the data it advances - RCV.NXT over the data accepted, and adjusts RCV.WND as - apporopriate to the current buffer availability. The total of - RCV.NXT and RCV.WND should not be reduced. + [RFC 793] Page 74: - Please note the window management suggestions in section 3.7. + Once in the ESTABLISHED state, it is possible to deliver segment + text to user RECEIVE buffers. Text from segments can be moved + into buffers until either the buffer is full or the segment is + empty. If the segment empties and carries an PUSH flag, then + the user is informed, when the buffer is returned, that a PUSH + has been received. - Send an acknowledgment of the form: + When the TCP takes responsibility for delivering the data to the + user it must also acknowledge the receipt of the data. - + Once the TCP takes responsibility for the data it advances + RCV.NXT over the data accepted, and adjusts RCV.WND as + apporopriate to the current buffer availability. The total of + RCV.NXT and RCV.WND should not be reduced. - This acknowledgment should be piggybacked on a segment being - transmitted if possible without incurring undue delay. + Please note the window management suggestions in section 3.7. + + Send an acknowledgment of the form: + + + + This acknowledgment should be piggybacked on a segment being + transmitted if possible without incurring undue delay. */ +///////////////////////////////////////////////////////////////////// + void Connection::State::process_segment(Connection& tcp, TCP::Packet_ptr in) { - assert(in->has_data()); - - auto& tcb = tcp.tcb(); - int length = in->data_length(); - debug(" Received packet with DATA-LENGTH: %i. Add to receive buffer. \n", length); - if(!tcp.add_to_receive_buffer(in)) { - tcp.signal_error({"Receive buffer is full!"}); // Redo to BufferException? - return; // Don't ACK, sender need to resend. - } - tcb.RCV.NXT += length; - auto snd_nxt = tcb.SND.NXT; - debug2(" Advanced RCV.NXT: %u. SND.NXT = %u \n", tcb.RCV.NXT, snd_nxt); - if(in->isset(PSH)) { - debug(" Packet carries PUSH. Notify user.\n"); - tcp.signal_receive(true); - } else if(tcp.receive_buffer().full()) { - // Buffer is now full - debug(" Receive buffer is full. Notify user. \n"); - tcp.signal_receive(false); - } - /* - Once the TCP takes responsibility for the data it advances - RCV.NXT over the data accepted, and adjusts RCV.WND as - apporopriate to the current buffer availability. The total of - RCV.NXT and RCV.WND should not be reduced. - */ - if(tcb.SND.NXT == snd_nxt) { - tcp.outgoing_packet()->set_seq(tcb.SND.NXT).set_ack(tcb.RCV.NXT).set_flag(ACK); - tcp.transmit(); - } else { - debug2(" SND.NXT > snd_nxt, this packet has already been acknowledged. \n"); - } - + assert(in->has_data()); + + auto& tcb = tcp.tcb(); + auto length = in->data_length(); + // Receive could result in a user callback. This is used to avoid sending empty ACK reply. + debug(" Received packet with DATA-LENGTH: %i. Add to receive buffer. \n", length); + tcb.RCV.NXT += length; + auto snd_nxt = tcb.SND.NXT; + if(tcp.read_request.buffer.capacity()) { + auto received = tcp.receive((uint8_t*)in->data(), in->data_length(), in->isset(PSH)); + Ensures(received == length); + } + + // [RFC 5681] + //tcb.SND.cwnd += std::min(length, tcp.SMSS()); + debug2(" Advanced RCV.NXT: %u. SND.NXT = %u \n", tcb.RCV.NXT, snd_nxt); + + if(tcb.SND.NXT == snd_nxt) { + auto packet = tcp.outgoing_packet(); + packet->set_seq(tcb.SND.NXT).set_ack(tcb.RCV.NXT).set_flag(ACK); + tcp.transmit(packet); + } + //if(tcp.can_send()) + // tcp.send_much(); + /*if(tcp.has_doable_job() and !tcp.is_queued()) { + printf(" Usable window: %i\n", tcp.usable_window()); + tcp.writeq_push(); + }*/ + + /* + WARNING/NOTE: + Not sure how "dangerous" the following is, and how big of a bottleneck it is. + Maybe has to be implemented with timers or something. + */ + + /* + Once the TCP takes responsibility for the data it advances + RCV.NXT over the data accepted, and adjusts RCV.WND as + apporopriate to the current buffer availability. The total of + RCV.NXT and RCV.WND should not be reduced. + */ + // no data has been sent during user callback + // TODO: A lot of cleanup / refactoring - this is messy. + /*if(snd_nxt == tcb.SND.NXT) { + // Piggyback ACK with outgoing data + if(tcp.has_doable_job() and !tcp.is_queued()) { + debug2(" Usable window: %i\n", tcp.usable_window()); + tcp.writeq_push(); + // we tried to push data, but nothing was written, reply the sender immediately + if(tcp.usable_window() == tcb.SND.WND) { + auto packet = tcp.outgoing_packet(); + packet->set_seq(tcb.SND.NXT).set_ack(tcb.RCV.NXT).set_flag(ACK); + tcp.transmit(packet); + } + } + // TODO: Selective ACK + // If no outgoing data right now - reply with ACK. + else { + debug2(" ACK. Window: %i, Queue: %u, is_queued: %s\n", + tcp.usable_window(), tcp.writeq.size(), tcp.is_queued() ? "true" : "false"); + auto packet = tcp.outgoing_packet(); + packet->set_seq(tcb.SND.NXT).set_ack(tcb.RCV.NXT).set_flag(ACK); + tcp.transmit(packet); + } + } + else { + debug2(" SND.NXT > snd_nxt, this packet has already been acknowledged. \n"); + }*/ } +///////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////// /* - 8. Check FIN -*/ + 8. Process FIN -/* - If the FIN bit is set, signal the user "connection closing" and - return any pending RECEIVEs with same message, advance RCV.NXT - over the FIN, and send an acknowledgment for the FIN. Note that - FIN implies PUSH for any segment text not yet delivered to the - user. + Process a packet with FIN, by signal disconnect, reply with ACK etc. + + [RFC 793] Page 75: + + If the FIN bit is set, signal the user "connection closing" and + return any pending RECEIVEs with same message, advance RCV.NXT + over the FIN, and send an acknowledgment for the FIN. Note that + FIN implies PUSH for any segment text not yet delivered to the + user. */ +///////////////////////////////////////////////////////////////////// void Connection::State::process_fin(Connection& tcp, TCP::Packet_ptr in) { - debug(" Processing FIN bit in STATE: %s \n", tcp.state().to_string().c_str()); - assert(in->isset(FIN)); - auto& tcb = tcp.tcb(); - tcp.signal_disconnect("Connection closing."); - // Advance RCV.NXT over the FIN? - tcb.RCV.NXT++; - //auto fin = in->data_length(); - //tcb.RCV.NXT += fin; - tcp.outgoing_packet()->set_ack(tcb.RCV.NXT).set_flag(ACK); - tcp.transmit(); - if(!tcp.receive_buffer().empty()) { - tcp.signal_receive(true); - } + debug(" Processing FIN bit in STATE: %s \n", tcp.state().to_string().c_str()); + assert(in->isset(FIN)); + auto& tcb = tcp.tcb(); + tcp.signal_disconnect(Disconnect::CLOSING); + // Advance RCV.NXT over the FIN? + tcb.RCV.NXT++; + //auto fin = in->data_length(); + //tcb.RCV.NXT += fin; + auto packet = tcp.outgoing_packet(); + packet->set_ack(tcb.RCV.NXT).set_flag(ACK); + tcp.transmit(packet); + // signal the user + if(!tcp.read_request.buffer.empty()) + tcp.receive_disconnect(); } +///////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////// /* - Send a reset segment: + Send a reset segment. Used when aborting a connection. + + [RFC 793]: + Send a reset segment: - + All queued SENDs and RECEIVEs should be given "connection reset" notification; all segments queued for transmission (except for the RST formed above) or retransmission should be flushed, delete the TCB, enter CLOSED state, and return. */ +///////////////////////////////////////////////////////////////////// + void Connection::State::send_reset(Connection& tcp) { - tcp.send_buffer_.clear(); - tcp.outgoing_packet()->set_seq(tcp.tcb().SND.NXT).set_ack(0).set_flag(RST); - tcp.transmit(); + tcp.writeq_reset(); + auto packet = tcp.outgoing_packet(); + packet->set_seq(tcp.tcb().SND.NXT).set_ack(0).set_flag(RST); + tcp.transmit(packet); } +///////////////////////////////////////////////////////////////////// + +///////////////////////////////////////////////////////////////////// /* - Fallback. + STATE IMPLEMENTATION + + Here follows the implementation for all the different State-Event combinations. + + The implemenation is ordered in the following structure: + 1. Function + 2. State + + Function order: + OPEN + SEND + RECEIVE + CLOSE + ABORT + HANDLE (SEGMENT ARRIVES) + + State order: + State - this is the base state, works as fallback. + Closed + Listen + SynSent + SynReceived + Established + FinWait1 + FinWait2 + CloseWait + Closing + LastAck + TimeWait */ - -void Connection::State::open(Connection&, bool) { - throw TCPException{"Connection already exists."}; -} - -size_t Connection::State::send(Connection&, const char*, size_t, bool) { - throw TCPException{"Connection closing."}; -} - -size_t Connection::State::receive(Connection&, char*, size_t) { - throw TCPException{"Connection closing."}; -} - -void Connection::State::close(Connection&) { - throw TCPException{"Connection closing."}; -} - -void Connection::State::abort(Connection&) { - // Do nothing. -} ///////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////// /* - CLOSED + OPEN */ ///////////////////////////////////////////////////////////////////// -void Connection::Closed::open(Connection& tcp, bool active) { - if(active) { - // There is a remote host - if(!tcp.remote().is_empty()) { - auto& tcb = tcp.tcb(); - tcb.ISS = tcp.generate_iss(); - tcp.outgoing_packet()->set_seq(tcb.ISS).set_flag(SYN); - tcb.SND.UNA = tcb.ISS; - tcb.SND.NXT = tcb.ISS+1; - tcp.transmit(); - tcp.set_state(SynSent::instance()); - } else { - throw TCPException{"No remote host set."}; - } - } else { - tcp.set_state(Connection::Listen::instance()); - } +void Connection::State::open(Connection&, bool) { + throw TCPException{"Connection already exists."}; } -size_t Connection::Closed::send(Connection&, const char*, size_t, bool) { - throw TCPException{"Connection does not exist."}; +void Connection::Closed::open(Connection& tcp, bool active) { + if(active) { + // There is a remote host + if(!tcp.remote().is_empty()) { + auto& tcb = tcp.tcb(); + tcb.init(); + auto packet = tcp.outgoing_packet(); + packet->set_seq(tcb.ISS).set_flag(SYN); + + /* + Add MSS option. + */ + tcp.add_option(Option::MSS, packet); + + tcb.SND.UNA = tcb.ISS; + tcb.SND.NXT = tcb.ISS+1; + tcp.transmit(packet); + tcp.set_state(SynSent::instance()); + } else { + throw TCPException{"No remote host set."}; + } + } else { + tcp.set_state(Connection::Listen::instance()); + } } -State::Result Connection::Closed::handle(Connection& tcp, TCP::Packet_ptr in) { - if(in->isset(RST)) { - return OK; - } - if(!in->isset(ACK)) { - tcp.outgoing_packet()->set_seq(0).set_ack(in->seq() + in->data_length()).set_flags(RST | ACK); - } else { - tcp.outgoing_packet()->set_seq(in->ack()).set_flag(RST); - } - tcp.transmit(); - return OK; +void Connection::Listen::open(Connection& tcp, bool) { + if(!tcp.remote().is_empty()) { + auto& tcb = tcp.tcb(); + tcb.init(); + auto packet = tcp.outgoing_packet(); + packet->set_seq(tcb.ISS).set_flag(SYN); + tcb.SND.UNA = tcb.ISS; + tcb.SND.NXT = tcb.ISS+1; + tcp.transmit(packet); + tcp.set_state(SynSent::instance()); + } else { + throw TCPException{"No remote host set."}; + } } + + ///////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////// /* - LISTEN + SEND */ ///////////////////////////////////////////////////////////////////// -void Connection::Listen::open(Connection& tcp, bool) { - if(!tcp.remote().is_empty()) { - auto& tcb = tcp.tcb(); - tcb.ISS = tcp.generate_iss(); - tcp.outgoing_packet()->set_seq(tcb.ISS).set_flag(SYN); - tcb.SND.UNA = tcb.ISS; - tcb.SND.NXT = tcb.ISS+1; - tcp.transmit(); - tcp.set_state(SynSent::instance()); - } else { - throw TCPException{"No remote host set."}; - } +size_t Connection::State::send(Connection&, WriteBuffer&) { + throw TCPException{"Connection closing."}; } -size_t Connection::Listen::send(Connection&, const char*, size_t, bool) { - // TODO: Skip this? - /* - If the foreign socket is specified, then change the connection - from passive to active, select an ISS. Send a SYN segment, set - SND.UNA to ISS, SND.NXT to ISS+1. Enter SYN-SENT state. Data - associated with SEND may be sent with SYN segment or queued for - transmission after entering ESTABLISHED state. The urgent bit if - requested in the command must be sent with the data segments sent - as a result of this command. If there is no room to queue the - request, respond with "error: insufficient resources". If - Foreign socket was not specified, then return "error: foreign - socket unspecified". - */ - - return 0; +size_t Connection::Closed::send(Connection&, WriteBuffer&) { + throw TCPException{"Connection does not exist."}; } -void Connection::Listen::close(Connection& tcp) { - /* - Any outstanding RECEIVEs are returned with "error: closing" - responses. Delete TCB, enter CLOSED state, and return. - */ - - // tcp.signal_disconnect("Closing") - tcp.set_state(Closed::instance()); +size_t Connection::Listen::send(Connection&, WriteBuffer&) { + // TODO: Skip this? + /* + If the foreign socket is specified, then change the connection + from passive to active, select an ISS. Send a SYN segment, set + SND.UNA to ISS, SND.NXT to ISS+1. Enter SYN-SENT state. Data + associated with SEND may be sent with SYN segment or queued for + transmission after entering ESTABLISHED state. The urgent bit if + requested in the command must be sent with the data segments sent + as a result of this command. If there is no room to queue the + request, respond with "error: insufficient resources". If + Foreign socket was not specified, then return "error: foreign + socket unspecified". + */ + //if(tcp.remote().is_empty()) + // throw TCPException{"Foreign socket unspecified."}; + throw TCPException{"Cannot send on listening connection."}; + return 0; } +size_t Connection::SynSent::send(Connection&, WriteBuffer&) { + /* + Queue the data for transmission after entering ESTABLISHED state. + If no space to queue, respond with "error: insufficient + resources". + */ -State::Result Connection::Listen::handle(Connection& tcp, TCP::Packet_ptr in) { - if(in->isset(RST)) { - // ignore - return OK; - } - if(in->isset(ACK)) { - tcp.outgoing_packet()->set_seq(in->ack()).set_flag(RST); - tcp.transmit(); - return OK; - } - if(in->isset(SYN)) { - if(!tcp.signal_accept()) { - // TODO: Reject more gracefully? - return CLOSED; - } - auto& tcb = tcp.tcb(); - tcb.RCV.NXT = in->seq()+1; - tcb.IRS = in->seq(); - tcb.ISS = tcp.generate_iss(); - tcb.SND.NXT = tcb.ISS+1; - tcb.SND.UNA = tcb.ISS; - debug(" Received SYN Packet: %s TCB Updated:\n %s \n", - in->to_string().c_str(), tcp.tcb().to_string().c_str()); - - tcp.outgoing_packet()->set_seq(tcb.ISS).set_ack(tcb.RCV.NXT).set_flags(SYN | ACK); - tcp.transmit(); - tcp.set_state(SynReceived::instance()); - - return OK; - } - return OK; + return 0; // nothing written, indicates queue } -///////////////////////////////////////////////////////////////////// - -///////////////////////////////////////////////////////////////////// -/* - SYN-SENT -*/ -///////////////////////////////////////////////////////////////////// +size_t Connection::SynReceived::send(Connection&, WriteBuffer&) { + /* + Queue the data for transmission after entering ESTABLISHED state. + If no space to queue, respond with "error: insufficient + resources". + */ -size_t Connection::SynSent::send(Connection& tcp, const char* buffer, size_t n, bool push) { - /* - Queue the data for transmission after entering ESTABLISHED state. - If no space to queue, respond with "error: insufficient - resources". - */ - return tcp.write_to_send_buffer(buffer, n, push); + return 0; // nothing written, indicates queue } -void Connection::SynSent::close(Connection& tcp) { - /* - Delete the TCB and return "error: closing" responses to any - queued SENDs, or RECEIVEs. - */ +size_t Connection::Established::send(Connection& tcp, WriteBuffer& buffer) { + // if nothing in queue, try to write directly + if(!tcp.writeq.remaining_requests()) + return tcp.send(buffer); - // tcp.signal_disconnect("Closing") - tcp.set_state(Closed::instance()); + return 0; } +size_t Connection::CloseWait::send(Connection& tcp, WriteBuffer& buffer) { + // if nothing in queue, try to write directly + if(!tcp.writeq.remaining_requests()) + return tcp.send(buffer); -State::Result Connection::SynSent::handle(Connection& tcp, TCP::Packet_ptr in) { - auto& tcb = tcp.tcb(); - // 1. check ACK - if(in->isset(ACK)) { - // If SEG.ACK =< ISS, or SEG.ACK > SND.NXT - if(in->ack() <= tcb.ISS or in->ack() > tcb.SND.NXT) { - // send a reset - if(!in->isset(RST)) { - tcp.outgoing_packet()->set_seq(in->ack()).set_flag(RST); - tcp.transmit(); - return OK; - } - // (unless the RST bit is set, if so drop the segment and return) - else { - tcp.drop(in, "RST"); - return OK; - } - // If SND.UNA =< SEG.ACK =< SND.NXT then the ACK is acceptable. - } - } - // 2. check RST - if(in->isset(RST)) { - if(in->isset(ACK)) { - tcp.signal_error(TCPException{"Connection reset."}); - tcp.drop(in, "RST with acceptable ACK"); - return CLOSED; - } else { - tcp.drop(in, "RST"); - return OK; - } - /* - If the ACK was acceptable then signal the user "error: - connection reset", drop the segment, enter CLOSED state, - delete TCB, and return. Otherwise (no ACK) drop the segment - and return. - */ - - } - // 3. Check security - - // 4. check SYN - /* - This step should be reached only if the ACK is ok, or there is - no ACK, and it the segment did not contain a RST. - - If the SYN bit is on and the security/compartment and precedence - are acceptable then, RCV.NXT is set to SEG.SEQ+1, IRS is set to - SEG.SEQ. SND.UNA should be advanced to equal SEG.ACK (if there - is an ACK), and any segments on the retransmission queue which - are thereby acknowledged should be removed. - - If SND.UNA > ISS (our SYN has been ACKed), change the connection - state to ESTABLISHED, form an ACK segment - - - - and send it. Data or controls which were queued for - transmission may be included. If there are other controls or - text in the segment then continue processing at the sixth step - below where the URG bit is checked, otherwise return. - - Otherwise enter SYN-RECEIVED, form a SYN,ACK segment - - - - and send it. If there are other controls or text in the - segment, queue them for processing after the ESTABLISHED state - has been reached, return. - */ - /* - TODO: Fix this one according to the text above. - */ - if(in->isset(SYN)) { - tcb.RCV.NXT = in->seq()+1; - tcb.IRS = in->seq(); - tcb.SND.UNA = in->ack(); - - // (our SYN has been ACKed) - if(tcb.SND.UNA > tcb.ISS) { - tcp.set_state(Connection::Established::instance()); - TCP::Seq snd_nxt = tcb.SND.NXT; - tcp.signal_connect(); // NOTE: User callback - if(tcb.SND.NXT == snd_nxt) { - tcp.outgoing_packet()->set_seq(tcb.SND.NXT).set_ack(tcb.RCV.NXT).set_flag(ACK); - tcp.transmit(); - } - // State is now ESTABLISHED. - // Experimental, also makes unessecary process. - //in->clear_flag(SYN); - //tcp.state().handle(tcp, in); - - // 7. process segment text - if(in->has_data()) { - process_segment(tcp, in); - } - - // 8. check FIN bit - if(in->isset(FIN)) { - process_fin(tcp, in); - tcp.set_state(Connection::CloseWait::instance()); - return OK; - } - return OK; - } - // Otherwise enter SYN-RECEIVED, form a SYN,ACK segment - else { - tcp.outgoing_packet()->set_seq(tcb.ISS).set_ack(tcb.RCV.NXT).set_flags(SYN | ACK); - tcp.transmit(); - tcp.set_state(Connection::SynReceived::instance()); - if(in->has_data()) { - tcp.add_to_receive_buffer(in); - // Advance RCV.NXT ?? - tcb.RCV.NXT += in->data_length(); - } - return OK; - /* - If there are other controls or text in the - segment, queue them for processing after the ESTABLISHED state - has been reached, return. - - HOW? return tcp.receive(in); ? - */ - } - } - tcp.drop(in); - return OK; + return 0; } + ///////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////// /* - SYN-RECEIVED + RECEIVE */ ///////////////////////////////////////////////////////////////////// -size_t Connection::SynReceived::send(Connection& tcp, const char* buffer, size_t n, bool push) { - /* - Queue the data for transmission after entering ESTABLISHED state. - If no space to queue, respond with "error: insufficient - resources". - */ - return tcp.write_to_send_buffer(buffer, n, push); +void Connection::State::receive(Connection&, ReadBuffer&) { + throw TCPException{"Connection closing."}; } -void Connection::SynReceived::close(Connection& tcp) { - /* - If no SENDs have been issued and there is no pending data to send, - then form a FIN segment and send it, and enter FIN-WAIT-1 state; - otherwise queue for processing after entering ESTABLISHED state. - */ - // Dont know how to queue for close for processing... - auto& tcb = tcp.tcb(); - tcp.outgoing_packet()->set_seq(tcb.SND.NXT++).set_ack(tcb.RCV.NXT).set_flags(ACK | FIN); - tcp.transmit(); - tcp.set_state(Connection::FinWait1::instance()); +void Connection::Established::receive(Connection& tcp, ReadBuffer& buffer) { + tcp.receive(buffer); } -void Connection::SynReceived::abort(Connection& tcp) { - send_reset(tcp); +void Connection::FinWait1::receive(Connection& tcp, ReadBuffer& buffer) { + tcp.receive(buffer); } -State::Result Connection::SynReceived::handle(Connection& tcp, TCP::Packet_ptr in) { - // 1. check sequence - if(! check_seq(tcp, in) ) { - return OK; - } - // 2. check RST - if(in->isset(RST)) { - /* - If this connection was initiated with a passive OPEN (i.e., - came from the LISTEN state), then return this connection to - LISTEN state and return. The user need not be informed. If - this connection was initiated with an active OPEN (i.e., came - from SYN-SENT state) then the connection was refused, signal - the user "connection refused". In either case, all segments - on the retransmission queue should be removed. And in the - active OPEN case, enter the CLOSED state and delete the TCB, - and return. - */ - // Since we create a new connection when it starts listening, we don't wanna do this, but just delete it. - - if(tcp.prev_state().to_string() == Connection::SynSent::instance().to_string()) { - tcp.signal_disconnect("Connection refused."); - } - - return CLOSED; - } - // 3. check security - - // 4. check SYN - if( in->isset(SYN) ) { - unallowed_syn_reset_connection(tcp, in); - return CLOSED; - } - - // 5. check ACK - if(in->isset(ACK)) { - auto& tcb = tcp.tcb(); - /* - If SND.UNA =< SEG.ACK =< SND.NXT then enter ESTABLISHED state - and continue processing. - */ - if(tcb.SND.UNA <= in->ack() and in->ack() <= tcb.SND.NXT) { - debug(" SND.UNA =< SEG.ACK =< SND.NXT, continue in ESTABLISHED. \n"); - tcp.set_state(Connection::Established::instance()); - tcp.signal_connect(); // NOTE: User callback - return tcp.state().handle(tcp,in); // TODO: Fix. This is kinda bad, need to make the above steps again. - } - /* - If the segment acknowledgment is not acceptable, form a - reset segment, and send it. - */ - else { - tcp.outgoing_packet()->set_seq(in->ack()).set_flag(RST); - tcp.transmit(); - } - } - // ACK is missing - else { - tcp.drop(in, "SYN-RCV: !ACK"); - return OK; - } - - // 8. check FIN - if(in->isset(FIN)) { - process_fin(tcp, in); - tcp.set_state(Connection::CloseWait::instance()); - return OK; - } - return OK; +void Connection::FinWait2::receive(Connection& tcp, ReadBuffer& buffer) { + tcp.receive(buffer); +} + +void Connection::CloseWait::receive(Connection& tcp, ReadBuffer& buffer) { + tcp.receive(buffer); } + ///////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////// /* - ESTABLISHED + CLOSE */ ///////////////////////////////////////////////////////////////////// -/* - Segmentize the buffer and send it with a piggybacked - acknowledgment (acknowledgment value = RCV.NXT). If there is - insufficient space to remember this buffer, simply return "error: - insufficient resources". - - If the urgent flag is set, then SND.UP <- SND.NXT-1 and set the - urgent pointer in the outgoing segments. -*/ -size_t Connection::Established::send(Connection& tcp, const char* buffer, size_t n, bool push) { - debug(" Sending data with the length of %u. PUSH: %d \n", n, push); - auto bytes_written = tcp.write_to_send_buffer(buffer, n, push); - tcp.transmit(); - return bytes_written; - /* - 3.7 Data Communication - The sender of data keeps track of the next sequence number to use in - the variable SND.NXT. The receiver of data keeps track of the next - sequence number to expect in the variable RCV.NXT. The sender of data - keeps track of the oldest unacknowledged sequence number in the - variable SND.UNA. If the data flow is momentarily idle and all data - sent has been acknowledged then the three variables will be equal. - - When the sender creates a segment and transmits it the sender advances - SND.NXT. When the receiver accepts a segment it advances RCV.NXT and - sends an acknowledgment. When the data sender receives an - acknowledgment it advances SND.UNA. The extent to which the values of - these variables differ is a measure of the delay in the communication. - The amount by which the variables are advanced is the length of the - data in the segment. Note that once in the ESTABLISHED state all - segments must carry current acknowledgment information. - */ +void Connection::State::close(Connection&) { + throw TCPException{"Connection closing."}; } -size_t Connection::Established::receive(Connection& tcp, char* buffer, size_t n) { - return tcp.read_from_receive_buffer(buffer, n); +void Connection::Listen::close(Connection& tcp) { + /* + Any outstanding RECEIVEs are returned with "error: closing" + responses. Delete TCB, enter CLOSED state, and return. + */ + // tcp.signal_disconnect("Closing") + tcp.set_state(Closed::instance()); } -void Connection::Established::close(Connection& tcp) { - auto& tcb = tcp.tcb(); - tcp.outgoing_packet()->set_seq(tcb.SND.NXT++).set_ack(tcb.RCV.NXT).set_flags(ACK | FIN); - tcp.transmit(); - tcp.set_state(Connection::FinWait1::instance()); +void Connection::SynSent::close(Connection& tcp) { + /* + Delete the TCB and return "error: closing" responses to any + queued SENDs, or RECEIVEs. + */ + // tcp.signal_disconnect("Closing") + tcp.set_state(Closed::instance()); } -void Connection::Established::abort(Connection& tcp) { - send_reset(tcp); +void Connection::SynReceived::close(Connection& tcp) { + /* + If no SENDs have been issued and there is no pending data to send, + then form a FIN segment and send it, and enter FIN-WAIT-1 state; + otherwise queue for processing after entering ESTABLISHED state. + */ + // Dont know how to queue for close for processing... + auto& tcb = tcp.tcb(); + auto packet = tcp.outgoing_packet(); + packet->set_seq(tcb.SND.NXT++).set_ack(tcb.RCV.NXT).set_flags(ACK | FIN); + tcp.transmit(packet); + tcp.set_state(Connection::FinWait1::instance()); } -State::Result Connection::Established::handle(Connection& tcp, TCP::Packet_ptr in) { - // 1. check SEQ - if(! check_seq(tcp, in) ) { - return OK; - } - - // 2. check RST - if( in->isset(RST) ) { - tcp.signal_disconnect("Connection reset."); - return CLOSED; // close - } - - // 3. check security - - // 4. check SYN - if( in->isset(SYN) ) { - unallowed_syn_reset_connection(tcp, in); - return CLOSED; - } +void Connection::Established::close(Connection& tcp) { + auto& tcb = tcp.tcb(); + auto packet = tcp.outgoing_packet(); + packet->set_seq(tcb.SND.NXT++).set_ack(tcb.RCV.NXT).set_flags(ACK | FIN); + tcp.transmit(packet); + tcp.set_state(Connection::FinWait1::instance()); +} - // 5. check ACK - if( ! check_ack(tcp, in) ) { - return OK; - } - // 6. check URG - DEPRECATED +void Connection::FinWait1::close(Connection&) { + /* + Strictly speaking, this is an error and should receive a "error: + connection closing" response. An "ok" response would be + acceptable, too, as long as a second FIN is not emitted (the first + FIN may be retransmitted though). + */ +} - // 7. proccess the segment text - if(in->has_data()) { - process_segment(tcp, in); - } +void Connection::FinWait2::close(Connection&) { + /* + Strictly speaking, this is an error and should receive a "error: + connection closing" response. An "ok" response would be + acceptable, too, as long as a second FIN is not emitted (the first + FIN may be retransmitted though). + */ +} - // 8. check FIN bit - if(in->isset(FIN)) { - process_fin(tcp, in); - tcp.set_state(Connection::CloseWait::instance()); - return CLOSE; - } - return OK; +void Connection::CloseWait::close(Connection& tcp) { + /* + Queue this request until all preceding SENDs have been + segmentized; then send a FIN segment, enter CLOSING state. + */ + auto& tcb = tcp.tcb(); + auto packet = tcp.outgoing_packet(); + packet->set_seq(tcb.SND.NXT++).set_ack(tcb.RCV.NXT).set_flags(ACK | FIN); + tcp.transmit(packet); + //tcp.set_state(Connection::Closing::instance()); + // Correction: [RFC 1122 p. 93] + tcp.set_state(Connection::LastAck::instance()); } + ///////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////// /* - FIN-WAIT-1 + ABORT */ ///////////////////////////////////////////////////////////////////// -size_t Connection::FinWait1::receive(Connection& tcp, char* buffer, size_t n) { - return tcp.read_from_receive_buffer(buffer, n); +void Connection::State::abort(Connection&) { + // Do nothing. } -void Connection::FinWait1::close(Connection&) { +void Connection::SynReceived::abort(Connection& tcp) { + send_reset(tcp); +} +void Connection::Established::abort(Connection& tcp) { + send_reset(tcp); } void Connection::FinWait1::abort(Connection& tcp) { - send_reset(tcp); + send_reset(tcp); } -State::Result Connection::FinWait1::handle(Connection& tcp, TCP::Packet_ptr in) { - // 1. Check sequence number - if(! check_seq(tcp, in) ) { - return OK; - } - - // 2. check RST - if( in->isset(RST) ) { - tcp.signal_disconnect("Connection reset."); - return CLOSED; // close - } - - // 4. check SYN - if( in->isset(SYN) ) { - unallowed_syn_reset_connection(tcp, in); - return CLOSED; - } - - // 5. check ACK - if( ! check_ack(tcp, in) ) { - return OK; - } - /* - In addition to the processing for the ESTABLISHED state, if - our FIN is now acknowledged then enter FIN-WAIT-2 and continue - processing in that state. - */ - debug2(" Current TCB:\n %s \n", tcp.tcb().to_string().c_str()); - if(in->ack() == tcp.tcb().SND.NXT) { - // TODO: I guess or FIN is ACK'ed..? - tcp.set_state(Connection::FinWait2::instance()); - return tcp.state().handle(tcp, in); // TODO: Is this OK? - } - - // 7. proccess the segment text - if(in->has_data()) { - process_segment(tcp, in); - } +void Connection::FinWait2::abort(Connection& tcp) { + send_reset(tcp); +} - // 8. check FIN - if(in->isset(FIN)) { - process_fin(tcp, in); - debug2(" FIN isset. TCB:\n %s \n", tcp.tcb().to_string().c_str()); - /* - If our FIN has been ACKed (perhaps in this segment), then - enter TIME-WAIT, start the time-wait timer, turn off the other - timers; otherwise enter the CLOSING state. - */ - if(in->ack() == tcp.tcb().SND.NXT) { - // TODO: I guess or FIN is ACK'ed..? - tcp.set_state(TimeWait::instance()); - tcp.start_time_wait_timeout(); - } else { - tcp.set_state(Closing::instance()); - } - } - return OK; +void Connection::CloseWait::abort(Connection& tcp) { + send_reset(tcp); } + ///////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////// /* - FIN-WAIT-2 + HANDLE (SEGMENT ARRIVES) */ ///////////////////////////////////////////////////////////////////// -size_t Connection::FinWait2::receive(Connection& tcp, char* buffer, size_t n) { - return tcp.read_from_receive_buffer(buffer, n); -} - -void Connection::FinWait2::close(Connection&) { - -} - -void Connection::FinWait2::abort(Connection& tcp) { - send_reset(tcp); +State::Result Connection::Closed::handle(Connection& tcp, TCP::Packet_ptr in) { + if(in->isset(RST)) { + return OK; + } + auto packet = tcp.outgoing_packet(); + if(!in->isset(ACK)) { + packet->set_seq(0).set_ack(in->seq() + in->data_length()).set_flags(RST | ACK); + } else { + packet->set_seq(in->ack()).set_flag(RST); + } + tcp.transmit(packet); + return OK; } -State::Result Connection::FinWait2::handle(Connection& tcp, TCP::Packet_ptr in) { - // 1. check SEQ - if(! check_seq(tcp, in) ) { - return OK; - } - // 2. check RST - if( in->isset(RST) ) { - tcp.signal_disconnect("Connection reset."); - return CLOSED; // close +State::Result Connection::Listen::handle(Connection& tcp, TCP::Packet_ptr in) { + if(in->isset(RST)) { + // ignore + return OK; + } + if(in->isset(ACK)) { + auto packet = tcp.outgoing_packet(); + packet->set_seq(in->ack()).set_flag(RST); + tcp.transmit(packet); + return OK; + } + if(in->isset(SYN)) { + if(!tcp.signal_accept()) { + // TODO: Reject more gracefully? + return CLOSED; } + auto& tcb = tcp.tcb(); + tcb.RCV.NXT = in->seq()+1; + tcb.IRS = in->seq(); + tcb.init(); + tcb.SND.NXT = tcb.ISS+1; + tcb.SND.UNA = tcb.ISS; + debug(" Received SYN Packet: %s TCB Updated:\n %s \n", + in->to_string().c_str(), tcp.tcb().to_string().c_str()); - // 4. check SYN - if( in->isset(SYN) ) { - unallowed_syn_reset_connection(tcp, in); - return CLOSED; - } + auto packet = tcp.outgoing_packet(); + packet->set_seq(tcb.ISS).set_ack(tcb.RCV.NXT).set_flags(SYN | ACK); - // 5. check ACK - if( ! check_ack(tcp, in) ) { - return OK; - } + /* + Add MSS option. + TODO: Send even if we havent received MSS option? + */ + tcp.add_option(Option::MSS, packet); - // 7. proccess the segment text - if(in->has_data()) { - process_segment(tcp, in); - } + tcp.transmit(packet); + tcp.set_state(SynReceived::instance()); - // 8. check FIN - if(in->isset(FIN)) { - process_fin(tcp, in); - /* - Enter the TIME-WAIT state. Start the time-wait timer, turn off the other timers. - */ - tcp.set_state(Connection::TimeWait::instance()); - tcp.start_time_wait_timeout(); - } return OK; + } + return OK; } -///////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////// -/* - CLOSE-WAIT -*/ -///////////////////////////////////////////////////////////////////// +State::Result Connection::SynSent::handle(Connection& tcp, TCP::Packet_ptr in) { + // 1. check ACK + if(in->isset(ACK)) { + auto& tcb = tcp.tcb(); + // If SEG.ACK =< ISS, or SEG.ACK > SND.NXT + if(in->ack() <= tcb.ISS or in->ack() > tcb.SND.NXT) { + // send a reset + if(!in->isset(RST)) { + auto packet = tcp.outgoing_packet(); + packet->set_seq(in->ack()).set_flag(RST); + tcp.transmit(packet); + return OK; + } + // (unless the RST bit is set, if so drop the segment and return) + else { + tcp.drop(in, "RST"); + return OK; + } + // If SND.UNA =< SEG.ACK =< SND.NXT then the ACK is acceptable. + } else { + if(tcp.rttm.active) + tcp.rttm.stop(true); + } + } + + // 2. check RST + if(in->isset(RST)) { + if(in->isset(ACK)) { + tcp.signal_error(TCPException{"Connection reset."}); + tcp.drop(in, "RST with acceptable ACK"); + return CLOSED; + } else { + tcp.drop(in, "RST"); + return OK; + } + /* + If the ACK was acceptable then signal the user "error: + connection reset", drop the segment, enter CLOSED state, + delete TCB, and return. Otherwise (no ACK) drop the segment + and return. + */ + } + // 3. Check security -size_t Connection::CloseWait::send(Connection& tcp, const char* buffer, size_t n, bool push) { - debug(" Sending data with the length of %u. PUSH: %d \n", n, push); - auto bytes_written = tcp.write_to_send_buffer(buffer, n, push); - tcp.transmit(); - return bytes_written; -} + // 4. check SYN + /* + This step should be reached only if the ACK is ok, or there is + no ACK, and it the segment did not contain a RST. -size_t Connection::CloseWait::receive(Connection& tcp, char* buffer, size_t n) { - return tcp.read_from_receive_buffer(buffer, n); -} + If the SYN bit is on and the security/compartment and precedence + are acceptable then, RCV.NXT is set to SEG.SEQ+1, IRS is set to + SEG.SEQ. SND.UNA should be advanced to equal SEG.ACK (if there + is an ACK), and any segments on the retransmission queue which + are thereby acknowledged should be removed. -void Connection::CloseWait::close(Connection& tcp) { - /* - Queue this request until all preceding SENDs have been - segmentized; then send a FIN segment, enter CLOSING state. - */ - auto& tcb = tcp.tcb(); - tcp.outgoing_packet()->set_seq(tcb.SND.NXT++).set_ack(tcb.RCV.NXT).set_flags(ACK | FIN); - tcp.transmit(); - tcp.set_state(Connection::Closing::instance()); -} + If SND.UNA > ISS (our SYN has been ACKed), change the connection + state to ESTABLISHED, form an ACK segment -void Connection::CloseWait::abort(Connection& tcp) { - send_reset(tcp); -} + -State::Result Connection::CloseWait::handle(Connection& tcp, TCP::Packet_ptr in) { - // 1. check SEQ - if(! check_seq(tcp, in) ) { - return OK; + and send it. Data or controls which were queued for + transmission may be included. If there are other controls or + text in the segment then continue processing at the sixth step + below where the URG bit is checked, otherwise return. + + Otherwise enter SYN-RECEIVED, form a SYN,ACK segment + + + + and send it. If there are other controls or text in the + segment, queue them for processing after the ESTABLISHED state + has been reached, return. + */ + + // TODO: Fix this one according to the text above. + if(in->isset(SYN)) { + auto& tcb = tcp.tcb(); + tcb.RCV.NXT = in->seq()+1; + tcb.IRS = in->seq(); + tcb.SND.UNA = in->ack(); + + //tcp.rtx_ack(in->ack()); + + // (our SYN has been ACKed) + if(tcb.SND.UNA > tcb.ISS) { + tcp.set_state(Connection::Established::instance()); + // Correction: [RFC 1122 p. 94] + tcb.SND.WND = in->win(); + tcb.SND.WL1 = in->seq(); + tcb.SND.WL2 = in->ack(); + // end of correction + + TCP::Seq snd_nxt = tcb.SND.NXT; + tcp.signal_connect(); // NOTE: User callback + + if(tcb.SND.NXT == snd_nxt) { + auto packet = tcp.outgoing_packet(); + packet->set_seq(tcb.SND.NXT).set_ack(tcb.RCV.NXT).set_flag(ACK); + tcp.transmit(packet); + } + // State is now ESTABLISHED. + // Experimental, also makes unessecary process. + //in->clear_flag(SYN); + //tcp.state().handle(tcp, in); + + // 7. process segment text + if(in->has_data()) { + process_segment(tcp, in); + } + + // 8. check FIN bit + if(in->isset(FIN)) { + process_fin(tcp, in); + tcp.set_state(Connection::CloseWait::instance()); + return OK; + } + return OK; } + // Otherwise enter SYN-RECEIVED, form a SYN,ACK segment + else { + auto packet = tcp.outgoing_packet(); + packet->set_seq(tcb.ISS).set_ack(tcb.RCV.NXT).set_flags(SYN | ACK); + tcp.transmit(packet); + tcp.set_state(Connection::SynReceived::instance()); + if(in->has_data()) { + process_segment(tcp, in); + } + return OK; + /* + If there are other controls or text in the + segment, queue them for processing after the ESTABLISHED state + has been reached, return. - // 2. check RST - if( in->isset(RST) ) { - tcp.signal_disconnect("Connection reset."); - return CLOSED; // close + HOW? return tcp.receive(in); ? + */ } + } + tcp.drop(in); + return OK; +} - // 4. check SYN - if( in->isset(SYN) ) { - unallowed_syn_reset_connection(tcp, in); - return CLOSED; - } - // 5. check ACK - if( ! check_ack(tcp, in) ) { - return OK; +State::Result Connection::SynReceived::handle(Connection& tcp, TCP::Packet_ptr in) { + // 1. check sequence + if(! check_seq(tcp, in) ) { + return OK; + } + // 2. check RST + if(in->isset(RST)) { + /* + If this connection was initiated with a passive OPEN (i.e., + came from the LISTEN state), then return this connection to + LISTEN state and return. The user need not be informed. If + this connection was initiated with an active OPEN (i.e., came + from SYN-SENT state) then the connection was refused, signal + the user "connection refused". In either case, all segments + on the retransmission queue should be removed. And in the + active OPEN case, enter the CLOSED state and delete the TCB, + and return. + */ + // Since we create a new connection when it starts listening, we don't wanna do this, but just delete it. + // TODO: Remove string comparision + if(tcp.prev_state().to_string() == Connection::SynSent::instance().to_string()) { + tcp.signal_disconnect(Disconnect::REFUSED); } - // 7. proccess the segment text - // This should not occur, since a FIN has been received from the remote side. Ignore the segment text. + return CLOSED; + } + // 3. check security - // 8. check FIN - if(in->isset(FIN)) { - process_fin(tcp, in); - // Remain in state - return OK; + // 4. check SYN + if( in->isset(SYN) ) { + unallowed_syn_reset_connection(tcp, in); + return CLOSED; + } + + // 5. check ACK + if(in->isset(ACK)) { + auto& tcb = tcp.tcb(); + /* + If SND.UNA =< SEG.ACK =< SND.NXT then enter ESTABLISHED state + and continue processing. + */ + if(tcb.SND.UNA <= in->ack() and in->ack() <= tcb.SND.NXT) { + debug(" SND.UNA =< SEG.ACK =< SND.NXT, continue in ESTABLISHED. \n"); + if(tcp.rttm.active) + tcp.rttm.stop(true); + tcp.set_state(Connection::Established::instance()); + + // Taken from acknowledge (without congestion control) + tcb.SND.UNA = in->ack(); + if(tcp.rttm.active) + tcp.rttm.stop(); + //tcp.rtx_ack(in->ack()); + + // 7. proccess the segment text + if(in->has_data()) { + process_segment(tcp, in); + } + + tcp.signal_connect(); // NOTE: User callback + + // 8. check FIN bit + if(in->isset(FIN)) { + process_fin(tcp, in); + tcp.set_state(Connection::CloseWait::instance()); + return CLOSE; + } } + /* + If the segment acknowledgment is not acceptable, form a + reset segment, and send it. + */ + else { + auto packet = tcp.outgoing_packet(); + packet->set_seq(in->ack()).set_flag(RST); + tcp.transmit(packet); + } + } + // ACK is missing + else { + tcp.drop(in, "SYN-RCV: !ACK"); + return OK; + } + + // 8. check FIN + if(in->isset(FIN)) { + process_fin(tcp, in); + tcp.set_state(Connection::CloseWait::instance()); return OK; + } + return OK; } -///////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////// -/* - CLOSING -*/ -///////////////////////////////////////////////////////////////////// +State::Result Connection::Established::handle(Connection& tcp, TCP::Packet_ptr in) { + // 1. check SEQ + if(! check_seq(tcp, in) ) { + return OK; + } -State::Result Connection::Closing::handle(Connection& tcp, TCP::Packet_ptr in) { - // 1. Check sequence number - if(! check_seq(tcp, in) ) { - return OK; - } + // 2. check RST + if( in->isset(RST) ) { + tcp.signal_disconnect(Disconnect::RESET); + return CLOSED; // close + } - // 2. check RST - if( in->isset(RST) ) { - return CLOSED; // close - } + // 3. check security - // 4. check SYN - if( in->isset(SYN) ) { - unallowed_syn_reset_connection(tcp, in); - return CLOSED; - } - - // 5. check ACK - if( ! check_ack(tcp, in)) { - return CLOSED; - } - /* - In addition to the processing for the ESTABLISHED state, if - the ACK acknowledges our FIN then enter the TIME-WAIT state, - otherwise ignore the segment. - */ - if(in->ack() == tcp.tcb().SND.NXT) { - // TODO: I guess or FIN is ACK'ed..? - tcp.set_state(TimeWait::instance()); - tcp.start_time_wait_timeout(); - } + // 4. check SYN + if( in->isset(SYN) ) { + unallowed_syn_reset_connection(tcp, in); + return CLOSED; + } + + // 5. check ACK + if( ! check_ack(tcp, in) ) { + return OK; + } + // 6. check URG - DEPRECATED + + // 7. proccess the segment text + if(in->has_data()) { + process_segment(tcp, in); + } + + // 8. check FIN bit + if(in->isset(FIN)) { + tcp.set_state(Connection::CloseWait::instance()); + process_fin(tcp, in); + return OK; + } + return OK; +} - // 7. proccess the segment text - // This should not occur, since a FIN has been received from the remote side. Ignore the segment text. - // 8. check FIN - if(in->isset(FIN)) { - process_fin(tcp, in); - // Remain in state - return OK; - } +State::Result Connection::FinWait1::handle(Connection& tcp, TCP::Packet_ptr in) { + // 1. Check sequence number + if(! check_seq(tcp, in) ) { return OK; + } + + // 2. check RST + if( in->isset(RST) ) { + tcp.signal_disconnect(Disconnect::RESET); + return CLOSED; // close + } + + // 4. check SYN + if( in->isset(SYN) ) { + unallowed_syn_reset_connection(tcp, in); + return CLOSED; + } + + // 5. check ACK + if( ! check_ack(tcp, in) ) { + return OK; + } + /* + In addition to the processing for the ESTABLISHED state, if + our FIN is now acknowledged then enter FIN-WAIT-2 and continue + processing in that state. + */ + debug2(" Current TCB:\n %s \n", tcp.tcb().to_string().c_str()); + if(in->ack() == tcp.tcb().SND.NXT) { + // TODO: I guess or FIN is ACK'ed..? + tcp.set_state(Connection::FinWait2::instance()); + return tcp.state().handle(tcp, in); // TODO: Is this OK? + } + + // 7. proccess the segment text + if(in->has_data()) { + process_segment(tcp, in); + } + + // 8. check FIN + if(in->isset(FIN)) { + process_fin(tcp, in); + debug2(" FIN isset. TCB:\n %s \n", tcp.tcb().to_string().c_str()); + /* + If our FIN has been ACKed (perhaps in this segment), then + enter TIME-WAIT, start the time-wait timer, turn off the other + timers; otherwise enter the CLOSING state. + */ + if(in->ack() == tcp.tcb().SND.NXT) { + // TODO: I guess or FIN is ACK'ed..? + tcp.set_state(TimeWait::instance()); + if(tcp.rtx_timer.active) + tcp.rtx_stop(); + tcp.start_time_wait_timeout(); + } else { + tcp.set_state(Closing::instance()); + } + } + return OK; } -///////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////// -/* - LAST-ACK -*/ -///////////////////////////////////////////////////////////////////// +State::Result Connection::FinWait2::handle(Connection& tcp, TCP::Packet_ptr in) { + // 1. check SEQ + if(! check_seq(tcp, in) ) { + return OK; + } + + // 2. check RST + if( in->isset(RST) ) { + tcp.signal_disconnect(Disconnect::RESET); + return CLOSED; // close + } + + // 4. check SYN + if( in->isset(SYN) ) { + unallowed_syn_reset_connection(tcp, in); + return CLOSED; + } + + // 5. check ACK + if( ! check_ack(tcp, in) ) { + return OK; + } -State::Result Connection::LastAck::handle(Connection& tcp, TCP::Packet_ptr in) { - // 1. Check sequence number - if(! check_seq(tcp, in) ) { - return OK; - } + // 7. proccess the segment text + if(in->has_data()) { + process_segment(tcp, in); + } - // 2. check RST - if( in->isset(RST) ) { - return CLOSED; // close - } + // 8. check FIN + if(in->isset(FIN)) { + process_fin(tcp, in); + /* + Enter the TIME-WAIT state. + Start the time-wait timer, turn off the other timers. + */ + tcp.set_state(Connection::TimeWait::instance()); + if(tcp.rtx_timer.active) + tcp.rtx_stop(); + tcp.start_time_wait_timeout(); + } + return OK; +} - // 4. check SYN - if( in->isset(SYN) ) { - unallowed_syn_reset_connection(tcp, in); - return CLOSED; - } - if( ! check_ack(tcp, in)) { - return CLOSED; - } +State::Result Connection::CloseWait::handle(Connection& tcp, TCP::Packet_ptr in) { + // 1. check SEQ + if(! check_seq(tcp, in) ) { + return OK; + } + + // 2. check RST + if( in->isset(RST) ) { + tcp.signal_disconnect(Disconnect::RESET); + return CLOSED; // close + } + + // 4. check SYN + if( in->isset(SYN) ) { + unallowed_syn_reset_connection(tcp, in); + return CLOSED; + } + + // 5. check ACK + if( ! check_ack(tcp, in) ) { + return OK; + } - // 7. proccess the segment text - // This should not occur, since a FIN has been received from the remote side. Ignore the segment text. + // 7. proccess the segment text + // This should not occur, since a FIN has been received from the remote side. Ignore the segment text. - // 8. check FIN - if(in->isset(FIN)) { - process_fin(tcp, in); - // Remain in state - return OK; - } + // 8. check FIN + if(in->isset(FIN)) { + process_fin(tcp, in); + // Remain in state return OK; + } + return OK; } -///////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////// -/* - TIME-WAIT -*/ -///////////////////////////////////////////////////////////////////// +State::Result Connection::Closing::handle(Connection& tcp, TCP::Packet_ptr in) { + // 1. Check sequence number + if(! check_seq(tcp, in) ) { + return OK; + } + + // 2. check RST + if( in->isset(RST) ) { + return CLOSED; // close + } + + // 4. check SYN + if( in->isset(SYN) ) { + unallowed_syn_reset_connection(tcp, in); + return CLOSED; + } + + // 5. check ACK + if( ! check_ack(tcp, in)) { + return CLOSED; + } + + /* + In addition to the processing for the ESTABLISHED state, if + the ACK acknowledges our FIN then enter the TIME-WAIT state, + otherwise ignore the segment. + */ + if(in->ack() == tcp.tcb().SND.NXT) { + // TODO: I guess or FIN is ACK'ed..? + tcp.set_state(TimeWait::instance()); + tcp.start_time_wait_timeout(); + } + + // 7. proccess the segment text + // This should not occur, since a FIN has been received from the remote side. Ignore the segment text. + + // 8. check FIN + if(in->isset(FIN)) { + process_fin(tcp, in); + // Remain in state + return OK; + } + return OK; +} -State::Result Connection::TimeWait::handle(Connection& tcp, TCP::Packet_ptr in) { - // 1. Check sequence number - if(! check_seq(tcp, in) ) { - return OK; - } - // 2. check RST - if( in->isset(RST) ) { - return CLOSED; // close - } +State::Result Connection::LastAck::handle(Connection& tcp, TCP::Packet_ptr in) { + // 1. Check sequence number + if(! check_seq(tcp, in) ) { + return OK; + } + return CLOSED; + + // 2. check RST + if( in->isset(RST) ) { + return CLOSED; // close + } + + // 4. check SYN + if( in->isset(SYN) ) { + unallowed_syn_reset_connection(tcp, in); + return CLOSED; + } + + if( ! check_ack(tcp, in)) { + return CLOSED; + } + + // 7. proccess the segment text + // This should not occur, since a FIN has been received from the remote side. Ignore the segment text. + + // 8. check FIN + if(in->isset(FIN)) { + process_fin(tcp, in); + // Remain in state + return OK; + } + return OK; +} - // 4. check SYN - if( in->isset(SYN) ) { - unallowed_syn_reset_connection(tcp, in); - return CLOSED; - } - - // 7. proccess the segment text - // This should not occur, since a FIN has been received from the remote side. Ignore the segment text. - - // 8. check FIN - if(in->isset(FIN)) { - process_fin(tcp, in); - // Remain in state - tcp.start_time_wait_timeout(); - return OK; - } + +State::Result Connection::TimeWait::handle(Connection& tcp, TCP::Packet_ptr in) { + // 1. Check sequence number + if(! check_seq(tcp, in) ) { + return OK; + } + + // 2. check RST + if( in->isset(RST) ) { + return CLOSED; // close + } + + // 4. check SYN + if( in->isset(SYN) ) { + unallowed_syn_reset_connection(tcp, in); + return CLOSED; + } + + // 7. proccess the segment text + // This should not occur, since a FIN has been received from the remote side. Ignore the segment text. + + // 8. check FIN + if(in->isset(FIN)) { + process_fin(tcp, in); + // Remain in state + tcp.start_time_wait_timeout(); return OK; + } + return OK; } ///////////////////////////////////////////////////////////////////// diff --git a/src/seed/Makefile b/src/seed/Makefile index ce30c13f3a..d23786918a 100644 --- a/src/seed/Makefile +++ b/src/seed/Makefile @@ -16,11 +16,11 @@ INSTALL = $(INCLUDEOS_INSTALL) # Compiler and linker options ################################################### -CAPABS_COMMON = -mstackrealign -msse3 +CAPABS_COMMON = -mstackrealign -fstack-protector-all -msse3 WARNS = -Wall -Wextra #-pedantic DEBUG_OPTS = -ggdb3 -v -OPTIONS = $(WARNS) $(CAPABS) +OPTIONS = $(WARNS) $(CAPABS) $(EXTRA_FLAGS) # External Libraries ################################################### @@ -35,18 +35,19 @@ LIBCXX = $(INSTALL)/libcxx/libc++.a $(INSTALL)/libcxx/libc++abi.a INC_NEWLIB=$(INSTALL)/newlib/include INC_LIBCXX=$(INSTALL)/libcxx/include -CPP = clang++-3.6 -target i686-elf +CC = clang-3.8 -target i686-elf +CPP = clang++-3.8 -target i686-elf ifndef LD_INC LD_INC = ld endif -INCLUDES = -I$(INC_LIBCXX) -I$(INSTALL)/api/sys -I$(INC_NEWLIB) -I$(INSTALL)/api -I$(LOCAL_INCLUDES) " " +INCLUDES = -I$(INC_LIBCXX) -I$(INSTALL)/api/sys -I$(INC_NEWLIB) -I$(INSTALL)/api -I$(INSTALL)/mod/GSL/include $(LOCAL_INCLUDES) all: CAPABS = $(CAPABS_COMMON) -O2 debug: CAPABS = $(CAPABS_COMMON) -O0 stripped: CAPABS = $(CAPABS_COMMON) -Oz -CPPOPTS = $(CAPABS) $(WARNS) -c -m32 -std=c++14 -fno-stack-protector $(INCLUDES) -D_LIBCPP_HAS_NO_THREADS=1 #-flto -fno-exceptions +CPPOPTS = $(CAPABS) $(WARNS) -c -m32 -std=c++14 $(INCLUDES) -D_LIBCPP_HAS_NO_THREADS=1 -D_GNU_SOURCE LDOPTS = -nostdlib -melf_i386 -N --script=$(INSTALL)/linker.ld # Objects @@ -59,6 +60,7 @@ CRTN_OBJ = $(INSTALL)/crt/crtn.o # Full link list OBJS = $(FILES:.cpp=.o) .service_name.o + LIBS = $(INSTALL)/os.a $(LIBCXX) $(INSTALL)/os.a $(LIBC_OBJ) $(LIBM_OBJ) $(LIBGCC) OS_PRE = $(CRTBEGIN_OBJ) $(CRTI_OBJ) @@ -70,6 +72,7 @@ DEPS = $(OBJS:.o=.d) ################################################### # A complete build includes: # - a "service", to be linked with OS-objects (OS included) +.PHONY: all stripped debug debug-info debug-all memdisk service all: service @@ -77,13 +80,24 @@ stripped: LDOPTS += -S #strip all stripped: CPPOPTS += -Oz stripped: service +# Build like "all" but with debugging output (i.e. the 'debug'-macro) enabled +debug-info: CAPABS += -UNO_DEBUG +debug-info: service -# The same, but with debugging symbols (OBS: Dramatically increases binary size) +# Build with debugging symbols (OBS: Dramatically increases binary size) debug: CCOPTS += $(DEBUG_OPTS) debug: CPPOPTS += $(DEBUG_OPTS) -debug: LDOPTS += -M --verbose -debug: OBJS += $(LIBG_OBJ) -debug: service #Don't wanna call 'all', since it strips debug info +debug: OBJ_LIST += $(LIBG_OBJ) +debug: CAPABS += -O0 +debug: service + +# Build with debugging symbols + debugging ouput, i.e. "debug" + "debug-info" +debug-all: CAPABS += -UNO_DEBUG +debug-all: CCOPTS += $(DEBUG_OPTS) +debug-all: CPPOPTS += $(DEBUG_OPTS) +debug-all: OBJ_LIST += $(LIBG_OBJ) +debug-all: CAPABS += -O0 +debug-all: service # Disk image as a section ################################################### @@ -126,9 +140,13 @@ crt%.o: $(INSTALL)/crt/crt%.s # General C++-files to object files %.o: %.cpp - @echo "\n>> Compiling OS object without header" + @echo "\n>> Compiling $<..." $(CPP) $(CPPOPTS) -o $@ $< +%.o: %.c + @echo "\n>> Compiling $<..." + $(CC) $(WARNS) $(CAPABS) $(EXTRA_FLAGS) -c -m32 -std=c11 -I$(INSTALL)/api/sys -I$(INC_NEWLIB) -o $@ $< + # AS-assembled object files %.o: %.s @echo "\n>> Assembling GNU 'as' files" diff --git a/src/util/async.cpp b/src/util/async.cpp new file mode 100644 index 0000000000..fdb67a3c53 --- /dev/null +++ b/src/util/async.cpp @@ -0,0 +1,88 @@ +#include + +#include + +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) + +inline unsigned roundup(unsigned n, unsigned div) { + return (n + div - 1) / div; +} + +void Async::upload_file( + Disk disk, + const Dirent& ent, + Connection conn, + on_after_func callback, + const size_t CHUNK_SIZE) +{ + disk_transfer(disk, ent, + [conn] (fs::buffer_t buffer, + size_t length, + next_func next) + { + // write chunk to TCP connection + conn->write(buffer.get(), length, + [length, next] (size_t n) { + + // if all data written, go to next chunk + debug("sock write: %u / %u\n", n, length); + next(n == length); + + }, true); + + }, callback, CHUNK_SIZE); +} + +void Async::disk_transfer( + Disk disk, + const Dirent& ent, + on_write_func write_func, + on_after_func callback, + const size_t CHUNK_SIZE) +{ + typedef std::function next_func_t; + auto next = std::make_shared (); + + *next = + [next, disk, ent, write_func, callback, CHUNK_SIZE] (size_t pos) { + + // number of write calls necessary + const size_t writes = roundup(ent.size(), CHUNK_SIZE); + + // done condition + if (pos >= writes) { + callback(fs::no_error, true); + return; + } + + // read chunk from file + disk->fs().read(ent, pos * CHUNK_SIZE, CHUNK_SIZE, + [next, pos, write_func, callback, CHUNK_SIZE] ( + fs::error_t err, + fs::buffer_t buffer, + uint64_t length) + { + debug(" len=%llu\n",length); + if (err) { + printf("%s\n", err.to_string().c_str()); + callback(err, false); + return; + } + + // call write callback with data + write_func(buffer, length, + [next, pos, callback] (bool good) { + // if the write succeeded, call next + if (likely(good)) + (*next)(pos+1); + else + // otherwise, fail + callback({fs::error_t::E_IO, "Write failed"}, false); + }); + }); + + }; + // start async loop + (*next)(0); +} diff --git a/src/virtio/block.cpp b/src/virtio/block.cpp index 534d1665bd..9de14f193e 100644 --- a/src/virtio/block.cpp +++ b/src/virtio/block.cpp @@ -28,65 +28,66 @@ #define FEAT(x) (1 << x) +// a deleter that does nothing +void null_deleter(uint8_t*) {}; + VirtioBlk::VirtioBlk(hw::PCI_Device& d) - : Virtio(d), - req(queue_size(0), 0, iobase()), - request_counter(0) + : Virtio(d), req(queue_size(0), 0, iobase()) { INFO("VirtioBlk", "Driver initializing"); - + uint32_t needed_features = - FEAT(VIRTIO_BLK_F_BLK_SIZE); + FEAT(VIRTIO_BLK_F_BLK_SIZE); negotiate_features(needed_features); - + CHECK(features() & FEAT(VIRTIO_BLK_F_BARRIER), - "Barrier is enabled"); + "Barrier is enabled"); CHECK(features() & FEAT(VIRTIO_BLK_F_SIZE_MAX), - "Size-max is known"); + "Size-max is known"); CHECK(features() & FEAT(VIRTIO_BLK_F_SEG_MAX), - "Seg-max is known"); + "Seg-max is known"); CHECK(features() & FEAT(VIRTIO_BLK_F_GEOMETRY), - "Geometry structure is used"); + "Geometry structure is used"); CHECK(features() & FEAT(VIRTIO_BLK_F_RO), - "Device is read-only"); + "Device is read-only"); CHECK(features() & FEAT(VIRTIO_BLK_F_BLK_SIZE), - "Block-size is known"); + "Block-size is known"); CHECK(features() & FEAT(VIRTIO_BLK_F_SCSI), - "SCSI is enabled :("); + "SCSI is enabled :("); CHECK(features() & FEAT(VIRTIO_BLK_F_FLUSH), - "Flush enabled"); - - + "Flush enabled"); + + CHECK ((features() & needed_features) == needed_features, - "Negotiated needed features"); - + "Negotiated needed features"); + // Step 1 - Initialize REQ queue auto success = assign_queue(0, (uint32_t) req.queue_desc()); CHECK(success, "Request queue assigned (0x%x) to device", - (uint32_t) req.queue_desc()); - + (uint32_t) req.queue_desc()); + // Step 3 - Fill receive queue with buffers // DEBUG: Disable INFO("VirtioBlk", "Queue size: %i\tRequest size: %u\n", req.size(), sizeof(request_t)); - + // Get device configuration get_config(); - - // Signal setup complete. + + // Signal setup complete. setup_complete((features() & needed_features) == needed_features); CHECK((features() & needed_features) == needed_features, "Signalled driver OK"); - + // Hook up IRQ handler (inherited from Virtio) auto del(delegate::from(this)); IRQ_manager::subscribe(irq(),del); - IRQ_manager::enable_irq(irq()); - + IRQ_manager::enable_irq(irq()); + // Done INFO("VirtioBlk", "Block device with %llu sectors capacity", - config.capacity); - //CHECK(config.status == VIRTIO_BLK_S_OK, "Link up\n"); - req.kick(); + config.capacity); + //CHECK(config.status == VIRTIO_BLK_S_OK, "Link up\n"); + //req.kick(); } void VirtioBlk::get_config() @@ -94,90 +95,174 @@ void VirtioBlk::get_config() Virtio::get_config(&config, sizeof(virtio_blk_config_t)); } -void VirtioBlk::irq_handler() -{ +void VirtioBlk::irq_handler() { + + IRQ_manager::eoi(irq()); debug2(" IRQ handler\n"); - //Virtio Std. § 4.1.5.5, steps 1-3 - + //Virtio Std. § 4.1.5.5, steps 1-3 + // Step 1. read ISR unsigned char isr = hw::inp(iobase() + VIRTIO_PCI_ISR); - + // Step 2. A) - one of the queues have changed - if (isr & 1) - { + if (isr & 1) { // This now means service RX & TX interchangeably service_RX(); } // Step 2. B) - if (isr & 2) - { + if (isr & 2) { debug("\t Configuration change:\n"); - - // Getting the MAC + status - //debug("\t Old status: 0x%x\n", config.status); + + // Getting the MAC + status + //debug("\t Old status: 0x%x\n", config.status); get_config(); //debug("\t New status: 0x%x \n", config.status); } - IRQ_manager::eoi(irq()); } -void VirtioBlk::service_RX() -{ - req.disable_interrupts(); - - uint32_t received = 0; - uint32_t len; - request_t* hdr; - blk_data_t* vbr; - //printf("service_RX() reading from VirtioBlk device\n"); +void VirtioBlk::handle(request_t* hdr) { + // check request response + blk_resp_t* resp = &hdr->resp; + // only call handler with data when the request was fullfilled + if (resp->status == 0) { + buffer_t buf; + // for partial results, we will just use the buffer as-is + if (resp->partial) { + buf = buffer_t(hdr->io.sector, null_deleter); + } + else { + // otherwise, create a shared copy of the data + // because we are giving this to the user + uint8_t* copy = new uint8_t[SECTOR_SIZE]; + memcpy(copy, hdr->io.sector, SECTOR_SIZE); + buf = buffer_t(copy, std::default_delete()); + } + // return buffer only as size is implicit + resp->handler(buf); + } + else { + // return empty shared ptr + hdr->resp.handler(buffer_t()); + } +} + +void VirtioBlk::service_RX() { - while ((hdr = (request_t*) req.dequeue(len)) != nullptr) - { - printf("service_RX() received %u bytes for sector %llu\n", - len, hdr->hdr.sector); - vbr = &hdr->data; - - printf("service_RX() received %u bytes data response\n", len); - printf("Received handler: %p\n", vbr->handler); - - uint8_t* copy = new uint8_t[SECTOR_SIZE]; - memcpy(copy, vbr->sector, SECTOR_SIZE); - auto buf = buffer_t(copy, std::default_delete()); + int handled = 0; + req.disable_interrupts(); + do { + auto tok = req.dequeue(); + if (!tok.data()) break; - printf("Calling handler: %p\n", vbr->handler); - (*vbr->handler)(buf); - delete vbr->handler; + // only handle the main header of each request + auto* hdr = (request_t*) tok.data(); + handle(hdr); + inflight--; handled++; + // delete request(!) + delete hdr; - received++; - } - if (received == 0) - { - //printf("service_RX() error processing requests\n"); - } + } while (true); + // only ship more if we have nothing more queued (??) + if (inflight == 0) { + // if we have lots of free space and jobs, ship many + bool shipped = false; + while (free_space() && !jobs.empty()) { + auto* vbr = jobs.front(); + jobs.pop_front(); + shipit(vbr); + shipped = true; + } + if (shipped) req.kick(); + } req.enable_interrupts(); + + //printf("inflight: %d handled: %d shipped: %d num_free: %u\n", + // inflight, handled, scnt, req.num_free()); } -void VirtioBlk::read (block_t blk, on_read_func func) -{ - // Virtio Std. § 5.1.6.3 - auto* vbr = new request_t(); +void VirtioBlk::shipit(request_t* vbr) { - vbr->hdr.type = VIRTIO_BLK_T_IN; - vbr->hdr.ioprio = 0; - vbr->hdr.sector = blk; - vbr->data.handler = new on_read_func(func); - vbr->data.status = VIRTIO_BLK_S_OK; + Token token1 { { (uint8_t*) &vbr->hdr, sizeof(scsi_header_t) }, Token::OUT }; + Token token2 { { (uint8_t*) &vbr->io, sizeof(blk_io_t) }, Token::IN }; + Token token3 { { (uint8_t*) &vbr->resp, 1 }, Token::IN }; // 1 status byte - printf("Enqueue handler: %p\n", vbr->data.handler); + std::array tokens {{ token1, token2, token3 }}; + req.enqueue(tokens); + inflight++; +} + +void VirtioBlk::read (block_t blk, on_read_func func) { + // Virtio Std. § 5.1.6.3 + auto* vbr = new request_t(blk, false, func); + debug("virtioblk: Enqueue blk %llu\n", blk); + // + if (free_space()) { + shipit(vbr); + req.kick(); + } + else { + jobs.push_back(vbr); + } +} +void VirtioBlk::read (block_t blk, size_t cnt, on_read_func func) { - req.enqueue(&vbr->hdr, sizeof(scsi_header_t), &vbr->data, sizeof(blk_data_t)); - req.kick(); + bool shipped = false; + // create big buffer for collecting all the disk data + uint8_t* bufdata = new uint8_t[block_size() * cnt]; + buffer_t bigbuf { bufdata, std::default_delete() }; + // (initialized) boolean array of partial jobs + auto results = std::make_shared (cnt); + + for (int i = cnt-1; i >= 0; i--) + { + // create a special request where we collect all the data + auto* vbr = new request_t(blk + i, true, + [this, i, func, results, bigbuf] (buffer_t buffer) { + // if the job was already completed, return early + if (*results == 0) { + printf("Job cancelled? results == 0, blk=%u\n", i); + return; + } + // validate partial result + if (buffer) { + *results -= 1; + // copy partial block + memcpy(bigbuf.get() + i * block_size(), buffer.get(), block_size()); + // check if we have all blocks + if (*results == 0) { + // finally, call user-provided callback + func(bigbuf); + } + } + else { + // if the partial result failed, cancel all + *results = 0; + // callback with no data + func(buffer_t()); + } + }); + debug("virtioblk: Enqueue blk %llu\n", blk + i); + // + if (free_space()) { + shipit(vbr); + shipped = true; + } + else + jobs.push_back(vbr); + } + // kick when we have enqueued stuff + if (shipped) req.kick(); } -VirtioBlk::buffer_t VirtioBlk::read_sync(block_t) +VirtioBlk::request_t::request_t(uint64_t blk, bool part, on_read_func cb) { - return buffer_t(); + hdr.type = VIRTIO_BLK_T_IN; + hdr.ioprio = 0; // reserved + hdr.sector = blk; + resp.status = VIRTIO_BLK_S_IOERR; + resp.partial = part; + resp.handler = cb; } diff --git a/src/virtio/console.cpp b/src/virtio/console.cpp index 480ec1bf54..697b316e55 100644 --- a/src/virtio/console.cpp +++ b/src/virtio/console.cpp @@ -26,74 +26,74 @@ extern "C" #define FEAT(x) (1 << x) VirtioCon::VirtioCon(hw::PCI_Device& d) - : Virtio(d), - rx(queue_size(0), 0, iobase()), - tx(queue_size(1), 1, iobase()), - ctl_rx(queue_size(2), 2, iobase()), - ctl_tx(queue_size(3), 3, iobase()) +: Virtio(d), + rx(queue_size(0), 0, iobase()), + tx(queue_size(1), 1, iobase()), + ctl_rx(queue_size(2), 2, iobase()), + ctl_tx(queue_size(3), 3, iobase()) { INFO("VirtioCon", "Driver initializing"); - + uint32_t needed_features = - FEAT(VIRTIO_CONSOLE_F_MULTIPORT); + FEAT(VIRTIO_CONSOLE_F_MULTIPORT); negotiate_features(needed_features); - + CHECK(features() & FEAT(VIRTIO_CONSOLE_F_SIZE), - "Valid console dimensions"); + "Valid console dimensions"); CHECK(features() & FEAT(VIRTIO_CONSOLE_F_MULTIPORT), - "Multiple ports support"); + "Multiple ports support"); CHECK(features() & FEAT(VIRTIO_CONSOLE_F_EMERG_WRITE), - "Emergency write support"); - + "Emergency write support"); + CHECK ((features() & needed_features) == needed_features, - "Negotiated needed features"); - + "Negotiated needed features"); + // Step 1 - Initialize queues auto success = assign_queue(0, (uint32_t) rx.queue_desc()); CHECK(success, "Receive queue assigned (0x%x) to device", - (uint32_t) rx.queue_desc()); - + (uint32_t) rx.queue_desc()); + success = assign_queue(1, (uint32_t) tx.queue_desc()); CHECK(success, "Transmit queue assigned (0x%x) to device", - (uint32_t) tx.queue_desc()); - + (uint32_t) tx.queue_desc()); + success = assign_queue(2, (uint32_t) ctl_rx.queue_desc()); CHECK(success, "Control rx queue assigned (0x%x) to device", - (uint32_t) ctl_rx.queue_desc()); - + (uint32_t) ctl_rx.queue_desc()); + success = assign_queue(3, (uint32_t) ctl_tx.queue_desc()); CHECK(success, "Control tx queue assigned (0x%x) to device", - (uint32_t) ctl_tx.queue_desc()); - + (uint32_t) ctl_tx.queue_desc()); + /* - success = assign_queue(4, (uint32_t) rx1.queue_desc()); - CHECK(success, "rx1 queue assigned (0x%x) to device", + success = assign_queue(4, (uint32_t) rx1.queue_desc()); + CHECK(success, "rx1 queue assigned (0x%x) to device", (uint32_t) rx1.queue_desc()); - - success = assign_queue(5, (uint32_t) tx1.queue_desc()); - CHECK(success, "tx1 queue assigned (0x%x) to device", + + success = assign_queue(5, (uint32_t) tx1.queue_desc()); + CHECK(success, "tx1 queue assigned (0x%x) to device", (uint32_t) tx1.queue_desc()); */ - + // Step 3 - Fill receive queue with buffers - INFO("VirtioCon", "Queue size rx: %d tx: %d\n", - rx.size(), tx.size()); - + INFO("VirtioCon", "Queue size rx: %d tx: %d\n", + rx.size(), tx.size()); + // Get device configuration get_config(); - - // Signal setup complete. + + // Signal setup complete. setup_complete((features() & needed_features) == needed_features); CHECK((features() & needed_features) == needed_features, "Signalled driver OK"); - + // Hook up IRQ handler (inherited from Virtio) auto del(delegate::from(this)); IRQ_manager::subscribe(irq(), del); - IRQ_manager::enable_irq(irq()); - + IRQ_manager::enable_irq(irq()); + // Done INFO("VirtioCon", "Console with size (%u, %u), %u ports", - config.cols, config.rows, config.max_nr_ports); + config.cols, config.rows, config.max_nr_ports); rx.kick(); } @@ -106,69 +106,69 @@ void VirtioCon::irq_handler() { debug2(" IRQ handler\n"); - //Virtio Std. § 4.1.5.5, steps 1-3 - + //Virtio Std. § 4.1.5.5, steps 1-3 + // Step 1. read ISR unsigned char isr = hw::inp(iobase() + VIRTIO_PCI_ISR); - + // Step 2. A) - one of the queues have changed if (isr & 1) - { - // This now means service RX & TX interchangeably - service_RX(); - } - + { + // This now means service RX & TX interchangeably + service_RX(); + } + // Step 2. B) if (isr & 2) - { - debug("\t Configuration change:\n"); - - //debug("\t Old status: 0x%x\n", config.status); - get_config(); - //debug("\t New status: 0x%x \n", config.status); - } + { + debug("\t Configuration change:\n"); + + //debug("\t Old status: 0x%x\n", config.status); + get_config(); + //debug("\t New status: 0x%x \n", config.status); + } IRQ_manager::eoi(irq()); } void VirtioCon::service_RX() { rx.disable_interrupts(); - + while (rx.new_incoming()) - { - uint32_t len = 0; - char* condata = (char*) rx.dequeue(&len); - - uint32_t dontcare; - rx.dequeue(&dontcare); - - if (condata) { - //printf("service_RX() received %u bytes from virtio console\n", len); - //printf("Data: %s\n", condata); - //vbr->handler(0, vbr->sector); - } - else - { - // acknowledgement - //printf("No data, just len = %d\n", len); + uint32_t len = 0; + char* condata = (char*) rx.dequeue(&len); + + uint32_t dontcare; + rx.dequeue(&dontcare); + + if (condata) + { + //printf("service_RX() received %u bytes from virtio console\n", len); + //printf("Data: %s\n", condata); + //vbr->handler(0, vbr->sector); + } + else + { + // acknowledgement + //printf("No data, just len = %d\n", len); + } } - } - + rx.enable_interrupts(); } void VirtioCon::write ( - const void* data, - size_t len) + const void* data, + size_t len) { char* heapdata = new char[len]; memcpy(heapdata, data, len); - + scatterlist sg[1]; sg[0].data = (void*) heapdata; sg[0].size = len; // +1? - + tx.enqueue(sg, 1, 0, (void*) data); tx.kick(); } diff --git a/src/virtio/virtio.cpp b/src/virtio/virtio.cpp index 50cbe5c438..1536889d0c 100644 --- a/src/virtio/virtio.cpp +++ b/src/virtio/virtio.cpp @@ -6,9 +6,9 @@ // 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. @@ -22,13 +22,13 @@ #include void Virtio::set_irq(){ - - //Get device IRQ + + //Get device IRQ uint32_t value = _pcidev.read_dword(PCI::CONFIG_INTR); if ((value & 0xFF) > 0 && (value & 0xFF) < 32){ - _irq = value & 0xFF; + _irq = value & 0xFF; } - + } @@ -36,70 +36,70 @@ Virtio::Virtio(hw::PCI_Device& dev) : _pcidev(dev), _virtio_device_id(dev.product_id() + 0x1040) { INFO("Virtio","Attaching to PCI addr 0x%x",_pcidev.pci_addr()); - + /** PCI Device discovery. Virtio std. §4.1.2 */ - - /** - Match vendor ID and Device ID : §4.1.2.2 + + /** + Match vendor ID and Device ID : §4.1.2.2 */ if (_pcidev.vendor_id() != hw::PCI_Device::VENDOR_VIRTIO) panic("This is not a Virtio device"); CHECK(true, "Vendor ID is VIRTIO"); - + bool _STD_ID = _virtio_device_id >= 0x1040 and _virtio_device_id < 0x107f; - bool _LEGACY_ID = _pcidev.product_id() >= 0x1000 + bool _LEGACY_ID = _pcidev.product_id() >= 0x1000 and _pcidev.product_id() <= 0x103f; - + CHECK(_STD_ID or _LEGACY_ID, "Device ID 0x%x is in a valid range (%s)", - _pcidev.product_id(), - _STD_ID ? ">= Virtio 1.0" : (_LEGACY_ID ? "Virtio LEGACY" : "INVALID")); - + _pcidev.product_id(), + _STD_ID ? ">= Virtio 1.0" : (_LEGACY_ID ? "Virtio LEGACY" : "INVALID")); + assert(_STD_ID or _LEGACY_ID); - - /** - Match Device revision ID. Virtio Std. §4.1.2.2 + + /** + Match Device revision ID. Virtio Std. §4.1.2.2 */ bool rev_id_ok = ((_LEGACY_ID and _pcidev.rev_id() == 0) or (_STD_ID and _pcidev.rev_id() > 0)); - - - CHECK(rev_id_ok and version_supported(_pcidev.rev_id()), - "Device Revision ID (0x%x) supported", _pcidev.rev_id()); - + + + CHECK(rev_id_ok and version_supported(_pcidev.rev_id()), + "Device Revision ID (0x%x) supported", _pcidev.rev_id()); + assert(rev_id_ok); // We'll try to continue if it's newer than supported. - + // Probe PCI resources and fetch I/O-base for device _pcidev.probe_resources(); - _iobase=_pcidev.iobase(); - + _iobase=_pcidev.iobase(); + CHECK(_iobase, "Unit has valid I/O base (0x%x)", _iobase); - + /** Device initialization. Virtio Std. v.1, sect. 3.1: */ - + // 1. Reset device reset(); INFO2("[*] Reset device"); - + // 2. Set ACKNOWLEGE status bit, and // 3. Set DRIVER status bit - + hw::outp(_iobase + VIRTIO_PCI_STATUS, - hw::inp(_iobase + VIRTIO_PCI_STATUS) | - VIRTIO_CONFIG_S_ACKNOWLEDGE | - VIRTIO_CONFIG_S_DRIVER); - + hw::inp(_iobase + VIRTIO_PCI_STATUS) | + VIRTIO_CONFIG_S_ACKNOWLEDGE | + VIRTIO_CONFIG_S_DRIVER); + // THE REMAINING STEPS MUST BE DONE IN A SUBCLASS // 4. Negotiate features (Read, write, read) - // => In the subclass (i.e. Only the Nic driver knows if it wants a mac) - // 5. @todo IF >= Virtio 1.0, set FEATURES_OK status bit - // 6. @todo IF >= Virtio 1.0, Re-read Device Status to ensure features are OK - // 7. Device specifig setup. - - // Where the standard isn't clear, we'll do our best to separate work + // => In the subclass (i.e. Only the Nic driver knows if it wants a mac) + // 5. @todo IF >= Virtio 1.0, set FEATURES_OK status bit + // 6. @todo IF >= Virtio 1.0, Re-read Device Status to ensure features are OK + // 7. Device specifig setup. + + // Where the standard isn't clear, we'll do our best to separate work // between this class and subclasses. - + //Fetch IRQ from PCI resource set_irq(); @@ -108,20 +108,20 @@ Virtio::Virtio(hw::PCI_Device& dev) enable_irq_handler(); - + INFO("Virtio", "Initialization complete"); - - // It would be nice if we new that all queues were the same size. + + // It would be nice if we new that all queues were the same size. // Then we could pass this size on to the device-specific constructor // But, it seems there aren't any guarantees in the standard. - - // @note this is "the Legacy interface" according to Virtio std. 4.1.4.8. + + // @note this is "the Legacy interface" according to Virtio std. 4.1.4.8. // uint32_t queue_size = hw::inpd(_iobase + 0x0C); - + /* printf(queue_size > 0 and queue_size != PCI_WTF ? - "\t [x] Queue Size : 0x%lx \n" : - "\t [ ] No qeuue Size? : 0x%lx \n" ,queue_size); */ - + "\t [x] Queue Size : 0x%lx \n" : + "\t [ ] No qeuue Size? : 0x%lx \n" ,queue_size); */ + } void Virtio::get_config(void* buf, int len){ @@ -136,16 +136,16 @@ void Virtio::reset(){ hw::outp(_iobase + VIRTIO_PCI_STATUS, 0); } -uint32_t Virtio::queue_size(uint16_t index){ +uint32_t Virtio::queue_size(uint16_t index){ hw::outpw(iobase() + VIRTIO_PCI_QUEUE_SEL, index); return hw::inpw(iobase() + VIRTIO_PCI_QUEUE_SIZE); } -#define BTOP(x) ((unsigned long)(x) >> PAGESHIFT) +#define BTOP(x) ((unsigned long)(x) >> PAGESHIFT) bool Virtio::assign_queue(uint16_t index, uint32_t queue_desc){ hw::outpw(iobase() + VIRTIO_PCI_QUEUE_SEL, index); - hw::outpd(iobase() + VIRTIO_PCI_QUEUE_PFN, BTOP(queue_desc)); - return hw::inpd(iobase() + VIRTIO_PCI_QUEUE_PFN) == BTOP(queue_desc); + hw::outpd(iobase() + VIRTIO_PCI_QUEUE_PFN, OS::page_nr_from_addr(queue_desc)); + return hw::inpd(iobase() + VIRTIO_PCI_QUEUE_PFN) == OS::page_nr_from_addr(queue_desc); } uint32_t Virtio::probe_features(){ @@ -175,39 +175,35 @@ void Virtio::default_irq_handler(){ printf("PRIVATE virtio IRQ handler: Call %i \n",calls++); printf("Old Features : 0x%x \n",_features); printf("New Features : 0x%x \n",probe_features()); - + unsigned char isr = hw::inp(_iobase + VIRTIO_PCI_ISR); printf("Virtio ISR: 0x%i \n",isr); printf("Virtio ISR: 0x%i \n",isr); - + IRQ_manager::eoi(_irq); - + } void Virtio::enable_irq_handler(){ //_irq=0; //Works only if IRQ2INTR(_irq), since 0 overlaps an exception. - - //auto del=delegate::from_method(this); + + //auto del=delegate::from_method(this); auto del(delegate::from(this)); - + IRQ_manager::subscribe(_irq,del); - - IRQ_manager::enable_irq(_irq); - - -} -/** void Virtio::enable_irq_handler(IRQ_manager::irq_delegate d){ - //_irq=0; //Works only if IRQ2INTR(_irq), since 0 overlaps an exception. - //IRQ_manager::set_handler(IRQ2INTR(_irq), irq_virtio_entry); - - IRQ_manager::subscribe(_irq,d); - IRQ_manager::enable_irq(_irq); - }*/ +} + +/** void Virtio::enable_irq_handler(IRQ_manager::irq_delegate d){ + //_irq=0; //Works only if IRQ2INTR(_irq), since 0 overlaps an exception. + //IRQ_manager::set_handler(IRQ2INTR(_irq), irq_virtio_entry); + IRQ_manager::subscribe(_irq,d); + IRQ_manager::enable_irq(_irq); + }*/ diff --git a/src/virtio/virtio_queue.cpp b/src/virtio/virtio_queue.cpp index ce63009139..06dca2b987 100644 --- a/src/virtio/virtio_queue.cpp +++ b/src/virtio/virtio_queue.cpp @@ -6,9 +6,9 @@ // 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. @@ -16,8 +16,9 @@ // limitations under the License. #define DEBUG // Allow debug -#define DEBUG2 +//#define DEBUG2 +#include #include #include #include @@ -26,41 +27,40 @@ #include -/** - Virtio Queue class, nested inside Virtio. - */ -#define ALIGN(x) (((x) + PAGE_SIZE) & ~PAGE_SIZE) -unsigned Virtio::Queue::virtq_size(unsigned int qsz) -{ - return ALIGN(sizeof(virtq_desc)*qsz + sizeof(u16)*(3 + qsz)) - + ALIGN(sizeof(u16)*3 + sizeof(virtq_used_elem)*qsz); +/** + Virtio Queue class, nested inside Virtio. +*/ +#define ALIGN(x) (((x) + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1)) +unsigned Virtio::Queue::virtq_size(unsigned int qsz) +{ + return ALIGN(sizeof(virtq_desc)*qsz + sizeof(u16)*(3 + qsz)) + + ALIGN(sizeof(u16)*3 + sizeof(virtq_used_elem)*qsz); } void Virtio::Queue::init_queue(int size, void* buf){ - // The buffer starts with is an array of queue descriptors + // The buffer starts with is an array of queue descriptors (i.e. tokens) _queue.desc = (virtq_desc*)buf; debug("\t * Queue desc @ 0x%lx \n ",(long)_queue.desc); // The available buffer starts right after the queue descriptors - _queue.avail = (virtq_avail*)((char*)buf + size*sizeof(virtq_desc)); + _queue.avail = (virtq_avail*)((char*)buf + size*sizeof(virtq_desc)); debug("\t * Queue avail @ 0x%lx \n ",(long)_queue.avail); // The used queue starts at the beginning of the next page // (This is a formula from sanos - don't know why it works, but it does - // align the used queue to the next page border) + // align the used queue to the next page border) _queue.used = (virtq_used*)(((uint32_t)&_queue.avail->ring[size] + - sizeof(uint16_t)+PAGESIZE-1) & ~(PAGESIZE -1)); + sizeof(uint16_t)+OS::page_size()-1) & ~(OS::page_size() -1)); debug("\t * Queue used @ 0x%lx \n ",(long)_queue.used); - + } +/** A default handler doing nothing. -/** A default handler doing nothing. - - It's here because we might not want to look at the data, e.g. for + It's here because we might not want to look at the data, e.g. for the VirtioNet TX-queue which will get used buffers in. */ int empty_handler(uint8_t* UNUSED(data),int UNUSED(size)) { debug(" Empty handler. DROP! "); @@ -69,226 +69,148 @@ int empty_handler(uint8_t* UNUSED(data),int UNUSED(size)) { /** Constructor */ Virtio::Queue::Queue(uint16_t size, uint16_t q_index, uint16_t iobase) - : _size(size),_size_bytes(virtq_size(size)),_iobase(iobase),_num_free(size), + : _size(size),_size_bytes(virtq_size(size)),_iobase(iobase), _free_head(0), _num_added(0),_last_used_idx(0),_pci_index(q_index), _data_handler(delegate(empty_handler)) { // Allocate page-aligned size and clear it void* buffer = memalign(PAGE_SIZE, _size_bytes); - memset(buffer, 0, _size_bytes); - + memset(buffer, 0, _size_bytes); + debug(">>> Virtio Queue of size %i (%li bytes) initializing \n", - _size,_size_bytes); + _size,_size_bytes); init_queue(size,buffer); - - // Chain buffers - debug("\t * Chaining buffers \n"); + + // Chain buffers + debug("\t * Chaining buffers \n"); for (int i=0; i> Virtio Queue setup complete. \n"); } + /** Ported more or less directly from SanOS. */ -int Virtio::Queue::enqueue(scatterlist sg[], uint32_t out, uint32_t in, void* UNUSED(data)){ - - uint16_t i,avail,head, prev = _free_head; - - - while (_num_free < out + in){ // Queue is full (we think) - //while( num_avail() >= _size) // Wait for Virtio - printf("Buffer full (%i avail," \ - " used.idx: %i, avail.idx: %i )\n", - _pci_index,num_avail(), - _queue.used->idx,_queue.avail->idx - ); - panic("Buffer full"); - } +int Virtio::Queue::enqueue(gsl::span buffers){ + debug ("Enqueuing %i tokens \n", buffers.size()); - // Remove buffers from the free list - _num_free -= out + in; - head = _free_head; - - - // (implicitly) Mark all outbound tokens as device-readable - for (i = _free_head; out; i = _queue.desc[i].next, out--) - { - _queue.desc[i].flags = VIRTQ_DESC_F_NEXT; - _queue.desc[i].addr = (uint64_t)sg->data; - _queue.desc[i].len = sg->size; + uint16_t last = _free_head; + uint16_t first = _free_head; + // Place each buffer in a token + for( auto buf : buffers ) { + debug (" buf @ %p \n", buffers.data()); - debug(" Enqueueing outbound: index %i len %li, next %i\n", - _pci_index,i,_queue.desc[i].len,_queue.desc[i].next); + // Set read / write flags + _queue.desc[_free_head].flags = + buf.direction() ? VIRTQ_DESC_F_NEXT : VIRTQ_DESC_F_NEXT | VIRTQ_DESC_F_WRITE; - prev = i; - sg++; - } - - // Mark all inbound tokens as device-writable - for (; in; i = _queue.desc[i].next, in--) - { - debug(" Enqueuing inbound \n"); - _queue.desc[i].flags = VIRTQ_DESC_F_NEXT | VIRTQ_DESC_F_WRITE; - _queue.desc[i].addr = (uint64_t)sg->data; - _queue.desc[i].len = sg->size; - prev = i; - sg++; - } - - // No continue on last buffer - _queue.desc[prev].flags &= ~VIRTQ_DESC_F_NEXT; - - - // Update free pointer - _free_head = i; - - // Set callback token - //vq->data[head] = data; - - // SanOS: Put entry in available array, but do not update avail->idx until sync - avail = (_queue.avail->idx + _num_added++) % _size; - _queue.avail->ring[avail] = head; - debug(" avail: %i \n",_pci_index,avail); - - // Notify about free buffers - //if (_num_free > 0) set_event(&vq->bufavail); - - return _num_free; -} -void Virtio::Queue::enqueue( - void* out, - uint32_t out_len, - void* in, - uint32_t in_len) -{ - int total = (out) ? 1 : 0; - total += (in) ? 1 : 0; - - if (_num_free < total) - { - // Queue is full (we think) - printf("Buffer full (%i avail," \ - " used.idx: %i, avail.idx: %i )\n", - _pci_index, num_avail(), - _queue.used->idx,_queue.avail->idx - ); - panic("Buffer full"); - } - - // Remove buffers from the free list - _num_free -= total; - // remember current head for later - uint16_t head = _free_head; - // the last buffer in queue - virtq_desc* last = nullptr; - - // (implicitly) Mark all outbound tokens as device-readable - if (out) - { - current().flags = VIRTQ_DESC_F_NEXT; - current().addr = (intptr_t) out; - current().len = out_len; - - debug(" Enqueueing outbound: index %u len %li, next %i\n", - _pci_index, head, current().len, current().next); - - last = ¤t(); - // go to next - go_next(); - } - - // Mark all inbound tokens as device-writable - if (in) - { - debug(" Enqueuing inbound \n"); - current().flags = VIRTQ_DESC_F_NEXT | VIRTQ_DESC_F_WRITE; - current().addr = (intptr_t) in; - current().len = in_len; - - last = ¤t(); - // go to next - go_next(); + // Assign raw buffer + _queue.desc[_free_head].addr = (uint64_t) buf.data(); + _queue.desc[_free_head].len = buf.size(); + + last = _free_head; + _free_head = _queue.desc[_free_head].next; } - + + _desc_in_flight += buffers.size(); + Ensures(_desc_in_flight <= size()); + // No continue on last buffer - last->flags &= ~VIRTQ_DESC_F_NEXT; - - // SanOS: Put entry in available array, but do not update avail->idx until sync - uint16_t avail = (_queue.avail->idx + _num_added++) % _size; - _queue.avail->ring[avail] = head; - debug(" avail: %u\n", _pci_index, avail); -} -void* Virtio::Queue::dequeue(uint32_t& len) -{ - // Return NULL if there are no more completed buffers in the queue - if (_last_used_idx == _queue.used->idx) - { - debug(" Can't dequeue - no used buffers \n",_pci_index); - return nullptr; - } + _queue.desc[last].flags &= ~VIRTQ_DESC_F_NEXT; - // Get next completed buffer - auto& e = _queue.used->ring[_last_used_idx % _size]; - debug2(" Releasing token %li. Len: %li\n",_pci_index, e.id, e.len); - void* data = (void*) _queue.desc[e.id].addr; - len = e.len; - - // Release buffer - release(e.id); - _last_used_idx++; - - return data; + // Place the head of this current chain in the avail ring + uint16_t avail_index = (_queue.avail->idx + _num_added) % _size; + + // we added a token + _num_added++; + + _queue.avail->ring[avail_index] = first; + + debug(" avail_index: %i size: %i, _free_head %i \n", + _pci_index, avail_index, size(), _free_head ); + + debug ("Free tokens: %i \n", num_free()); + + return buffers.size(); } void Virtio::Queue::release(uint32_t head) { + // Mark queue element "head" as free (the whole token chain) uint32_t i = head; - - //It's at least one token... - _num_free++; - //...possibly with a tail - while (_queue.desc[i].flags & VIRTQ_DESC_F_NEXT) - { - i = _queue.desc[i].next; - _num_free++; - } - + _desc_in_flight --; + + while (_queue.desc[i].flags & VIRTQ_DESC_F_NEXT) + { + i = _queue.desc[i].next; + _desc_in_flight --; + } + // Add buffers back to free list _queue.desc[i].next = _free_head; _free_head = head; - - // What happens here? - debug2(" desc[%i].next : %i \n",_pci_index,i,_queue.desc[i].next); + + debug("Descriptors in flight: %i \n", _desc_in_flight); + } -uint8_t* Virtio::Queue::dequeue(uint32_t* len){ +Virtio::Token Virtio::Queue::dequeue() { // Return NULL if there are no more completed buffers in the queue if (_last_used_idx == _queue.used->idx){ debug(" Can't dequeue - no used buffers \n",_pci_index); - return NULL; + return {{nullptr, 0}, Token::IN}; } + debug(" Dequeueing last_used index %i ",_pci_index, _last_used_idx); + + // Get next completed buffer + auto& e = _queue.used->ring[_last_used_idx % _size]; + debug(" Releasing token @%p, nr. %i Len: %i\n",_pci_index, &e, e.id, e.len); + + // Release buffer + release(e.id); + _last_used_idx++; + // return token: + return {{(uint8_t*) _queue.desc[e.id].addr, + (gsl::span::size_type) e.len }, Token::IN}; +} +std::vector Virtio::Queue::dequeue_chain() { + + std::vector result; + + // Return NULL if there are no more completed buffers in the queue + if (_last_used_idx == _queue.used->idx){ + debug(" Can't dequeue - no used buffers \n",_pci_index); + return result; + } + debug(" Dequeueing last_used index %i ",_pci_index, _last_used_idx); // Get next completed buffer auto* e = &_queue.used->ring[_last_used_idx % _size]; - *len = e->len; - debug2(" Releasing token %li. Len: %li\n",_pci_index,e->id, e->len); - uint8_t* data = (uint8_t*)_queue.desc[e->id].addr; - + auto* unchain = &_queue.desc[e->id]; + do + { + result.emplace_back( + Token::span{ (uint8_t*) unchain->addr, unchain->len }, Token::IN); + unchain = &_queue.desc[ unchain->next ]; + } + while (unchain->flags & VIRTQ_DESC_F_NEXT); + // Release buffer + debug(" Releasing token @%p, nr. %i Len: %i\n",_pci_index, e, e->id, e->len); release(e->id); _last_used_idx++; - return data; + return result; } -void Virtio::Queue::set_data_handler(delegate del){ - _data_handler=del; +void Virtio::Queue::set_data_handler(data_handler_t del) { + _data_handler = del; } void Virtio::Queue::disable_interrupts(){ @@ -300,16 +222,11 @@ void Virtio::Queue::enable_interrupts(){ } void Virtio::Queue::kick(){ - //__sync_synchronize (); - - // Atomically increment (maybe not necessary?) - //__sync_add_and_fetch(&(_queue.avail->idx),_num_added); - _queue.avail->idx += _num_added; - //__sync_synchronize (); - _num_added = 0; - + update_avail_idx(); + // Std. §3.2.1 pt. 4 + asm volatile("mfence" ::: "memory"); if (!(_queue.used->flags & VIRTQ_USED_F_NO_NOTIFY)){ debug(" Kicking virtio. Iobase 0x%x \n", _pci_index, _iobase); diff --git a/src/virtio/virtionet.cpp b/src/virtio/virtionet.cpp index dacbce937f..3e35e5d821 100644 --- a/src/virtio/virtionet.cpp +++ b/src/virtio/virtionet.cpp @@ -16,8 +16,8 @@ // limitations under the License. #define PRINT_INFO -//#define DEBUG // Allow debuging -//#define DEBUG2 +#define DEBUG // Allow debuging +#define DEBUG2 #include #include @@ -57,38 +57,38 @@ VirtioNet::VirtioNet(hw::PCI_Device& d) | (1 << VIRTIO_NET_F_STATUS); //| (1 << VIRTIO_NET_F_MRG_RXBUF); //Merge RX Buffers (Everything i 1 buffer) uint32_t wanted_features = needed_features; /*; - | (1 << VIRTIO_NET_F_CSUM) - | (1 << VIRTIO_F_ANY_LAYOUT) - | (1 << VIRTIO_NET_F_CTRL_VQ) - | (1 << VIRTIO_NET_F_GUEST_ANNOUNCE) - | (1 << VIRTIO_NET_F_CTRL_MAC_ADDR);*/ + | (1 << VIRTIO_NET_F_CSUM) + | (1 << VIRTIO_F_ANY_LAYOUT) + | (1 << VIRTIO_NET_F_CTRL_VQ) + | (1 << VIRTIO_NET_F_GUEST_ANNOUNCE) + | (1 << VIRTIO_NET_F_CTRL_MAC_ADDR);*/ negotiate_features(wanted_features); CHECK ((features() & needed_features) == needed_features, - "Negotiated needed features"); + "Negotiated needed features"); CHECK ((features() & wanted_features) == wanted_features, - "Negotiated wanted features"); + "Negotiated wanted features"); CHECK(features() & (1 << VIRTIO_NET_F_CSUM), - "Device handles packets w. partial checksum"); + "Device handles packets w. partial checksum"); CHECK(features() & (1 << VIRTIO_NET_F_GUEST_CSUM), - "Guest handles packets w. partial checksum"); + "Guest handles packets w. partial checksum"); CHECK(features() & (1 << VIRTIO_NET_F_CTRL_VQ), - "There's a control queue"); + "There's a control queue"); CHECK(features() & (1 << VIRTIO_F_ANY_LAYOUT), - "Queue can handle any header/data layout"); + "Queue can handle any header/data layout"); CHECK(features() & (1 << VIRTIO_F_RING_INDIRECT_DESC), - "We can use indirect descriptors"); + "We can use indirect descriptors"); CHECK(features() & (1 << VIRTIO_F_RING_EVENT_IDX), - "There's a Ring Event Index to use"); + "There's a Ring Event Index to use"); CHECK(features() & (1 << VIRTIO_NET_F_MQ), "There are multiple queue pairs"); @@ -97,29 +97,29 @@ VirtioNet::VirtioNet(hw::PCI_Device& d) printf("\t\t* max_virtqueue_pairs: 0x%x \n",_conf.max_virtq_pairs); CHECK(features() & (1 << VIRTIO_NET_F_MRG_RXBUF), - "Merge RX buffers"); + "Merge RX buffers"); // Step 1 - Initialize RX/TX queues auto success = assign_queue(0, (uint32_t)rx_q.queue_desc()); CHECK(success, "RX queue assigned (0x%x) to device", - (uint32_t)rx_q.queue_desc()); + (uint32_t)rx_q.queue_desc()); success = assign_queue(1, (uint32_t)tx_q.queue_desc()); CHECK(success, "TX queue assigned (0x%x) to device", - (uint32_t)tx_q.queue_desc()); + (uint32_t)tx_q.queue_desc()); // Step 2 - Initialize Ctrl-queue if it exists if (features() & (1 << VIRTIO_NET_F_CTRL_VQ)) { success = assign_queue(2, (uint32_t)tx_q.queue_desc()); CHECK(success, "CTRL queue assigned (0x%x) to device", - (uint32_t)ctrl_q.queue_desc()); + (uint32_t)ctrl_q.queue_desc()); } // Step 3 - Fill receive queue with buffers // DEBUG: Disable INFO("VirtioNet", "Adding %i receive buffers of size %i", - rx_q.size() / 2, Packet::MTU+sizeof(virtio_net_hdr)); + rx_q.size() / 2, bufsize()); for (int i = 0; i < rx_q.size() / 2; i++) add_receive_buffer(); @@ -137,7 +137,7 @@ VirtioNet::VirtioNet(hw::PCI_Device& d) get_config(); CHECK(_conf.mac.major > 0, "Valid Mac address: %s", - _conf.mac.str().c_str()); + _conf.mac.str().c_str()); // Step 7 - 9 - GSO: @todo Not using GSO features yet. @@ -159,26 +159,26 @@ VirtioNet::VirtioNet(hw::PCI_Device& d) }; -/** Port-ish from SanOS */ + int VirtioNet::add_receive_buffer(){ - virtio_net_hdr* hdr; - scatterlist sg[2]; + // Virtio Std. § 5.1.6.3 auto buf = bufstore_.get_raw_buffer(); - debug2(" Added receive-bufer @ 0x%lx \n", (uint32_t)buf); + debug2(" Added receive-bufer @ 0x%x \n", (uint32_t)buf); + + Token token1 { + {buf, sizeof(virtio_net_hdr)}, + Token::IN }; - hdr = (virtio_net_hdr*)buf; + Token token2 { + {buf + sizeof(virtio_net_hdr), (Token::size_type) (bufsize() - sizeof(virtio_net_hdr))}, + Token::IN }; - sg[0].data = hdr; + std::array tokens {{ token1, token2 }}; - //NOTE: using separate empty header doesn't work for RX, but it works for TX... - //sg[0].data = (void*)&empty_header; - sg[0].size = sizeof(virtio_net_hdr); - sg[1].data = buf + sizeof(virtio_net_hdr); - sg[1].size = Packet::MTU; - rx_q.enqueue(sg, 0, 2,buf); + rx_q.enqueue(tokens); return 0; } @@ -198,12 +198,9 @@ void VirtioNet::irq_handler(){ if (isr & 1){ // This now means service RX & TX interchangeably - service_RX(); - // We need a zipper-solution; we can't receive n packets before sending // anything - that's unfair. - - //service_TX(); + service_queues(); } // Step 2. B) @@ -219,75 +216,111 @@ void VirtioNet::irq_handler(){ } -void VirtioNet::service_RX(){ - debug2(" %i new packets, %i available tokens \n", - rx_q.new_incoming(),rx_q.num_avail()); - +void VirtioNet::service_queues(){ + debug2(" %i new packets \n", + rx_q.new_incoming()); /** For RX, we dequeue, add new buffers and let receiver is responsible for memory management (they know when they're done with the packet.) */ - int i = 0; + int dequeued_rx = 0; uint32_t len = 0; uint8_t* data; + int dequeued_tx = 0; rx_q.disable_interrupts(); + tx_q.disable_interrupts(); // A zipper, alternating between sending and receiving while(rx_q.new_incoming() or tx_q.new_incoming()){ // Do one RX-packet if (rx_q.new_incoming() ){ - data = rx_q.dequeue(&len); //BUG # 102? + sizeof(virtio_net_hdr); + + auto res = rx_q.dequeue(); //BUG # 102? + sizeof(virtio_net_hdr); + + data = (uint8_t*) res.data(); + len += res.size(); auto pckt_ptr = std::make_shared - (data+sizeof(virtio_net_hdr), // Offset buffer (bufstore knows the offset) - MTU()-sizeof(virtio_net_hdr), // Capacity - len - sizeof(virtio_net_hdr), release_buffer); // Size + (data + sizeof(virtio_net_hdr), // Offset buffer (bufstore knows the offseto) + bufsize()-sizeof(virtio_net_hdr), // Capacity + res.size() - sizeof(virtio_net_hdr), release_buffer); // Size _link_out(pckt_ptr); // Requeue a new buffer add_receive_buffer(); - i++; + dequeued_rx++; } - debug2(" Service loop about to kick RX if %i \n",i); - - if (i) - rx_q.kick(); // Do one TX-packet if (tx_q.new_incoming()){ - tx_q.dequeue(&len); + debug2(" Dequeing TX"); + tx_q.dequeue(); + dequeued_tx++; } - rx_q.enable_interrupts(); } - debug2(" Done servicing queues\n"); -} + debug2(" Service loop about to kick RX if %i \n", + dequeued_rx); + // Let virtio know we have increased receive capacity + if (dequeued_rx) + rx_q.kick(); -void VirtioNet::service_TX(){ - debug2(" %i transmitted, %i waiting packets\n", - tx_q.new_incoming(),tx_q.num_avail()); - uint32_t len = 0; - int i = 0; + rx_q.enable_interrupts(); + tx_q.enable_interrupts(); + + // If we have a transmit queue, eat from it, otherwise let the stack know we + // have increased transmit capacity + if (dequeued_tx) { + + debug("%i dequeued, transmitting backlog\n", dequeued_tx); + + // transmit as much as possible from the buffer + if (transmit_queue_){ + auto buf = transmit_queue_; + transmit_queue_ = 0; + transmit(buf); + }else{ + debug(" Transmit queue is empty \n"); + } + + // If we now emptied the buffer, offer packets to stack + if (!transmit_queue_ && tx_q.num_free() > 1) + transmit_queue_available_event_(tx_q.num_free() / 2); + else + debug(" No event: !transmit q %i, num_avail %i \n", + !transmit_queue_, tx_q.num_free()); + } + + debug(" Done servicing queues\n"); +} - /** For TX, just dequeue all incoming tokens. +void VirtioNet::add_to_tx_buffer(net::Packet_ptr pckt){ + if (transmit_queue_) + transmit_queue_->chain(pckt); + else + transmit_queue_ = pckt; + +#ifdef DEBUG + size_t chain_length = 1; + Packet_ptr next = transmit_queue_->tail(); + while (next) { + chain_length++; + next = next->tail(); + } +#endif - Sender allocated the buffer and is responsible for memory management. - @todo Sender doesn't know when the packet is transmitted; deal with it. */ - for (;i < tx_q.new_incoming(); i++) - tx_q.dequeue(&len); + debug("Buffering, %i packets chained \n", chain_length); - debug2("\t Dequeued %i packets \n",i); - // Deallocate buffer. } void VirtioNet::transmit(net::Packet_ptr pckt){ - debug2(" Enqueuing %lib of data. \n",pckt->len()); + debug2(" Enqueuing %ib of data. \n",pckt->size()); /** @note We have to send a virtio header first, then the packet. @@ -302,18 +335,47 @@ void VirtioNet::transmit(net::Packet_ptr pckt){ support VirtualBox */ - // A scatterlist for virtio-header + data - scatterlist sg[2]; + int transmitted = 0; + net::Packet_ptr tail {pckt}; + + // Transmit all we can directly + while (tx_q.num_free() and tail) { + debug("%i tokens left in TX queue \n", tx_q.num_free()); + on_exit_to_physical_(tail); + enqueue(tail); + tail = tail->detach_tail(); + transmitted++; + if (! tail) + break; + + } + + // Notify virtio about new packets + if (transmitted) { + tx_q.kick(); + } + + // Buffer the rest + if (tail) { + add_to_tx_buffer(tail); + + debug("Buffering remaining packets \n"); + } + +} + +void VirtioNet::enqueue(net::Packet_ptr pckt){ + // This setup requires all tokens to be pre-chained like in SanOS - sg[0].data = (void*)&empty_header; - sg[0].size = sizeof(virtio_net_hdr); - sg[1].data = (void*)pckt->buffer(); - sg[1].size = pckt->size(); + Token token1 {{(uint8_t*) &empty_header, sizeof(virtio_net_hdr)}, + Token::OUT }; - // Enqueue scatterlist, 2 pieces readable, 0 writable. - tx_q.enqueue(sg, 2, 0, 0); + Token token2 { {pckt->buffer(), (Token::size_type) pckt->size() }, Token::OUT }; - tx_q.kick(); + std::array tokens {{ token1, token2 }}; + + // Enqueue scatterlist, 2 pieces readable, 0 writable. + tx_q.enqueue(tokens); } diff --git a/test/GSL/Makefile b/test/GSL/Makefile new file mode 100644 index 0000000000..61014aa23f --- /dev/null +++ b/test/GSL/Makefile @@ -0,0 +1,22 @@ +################################################# +# IncludeOS SERVICE makefile # +################################################# + +# The name of your service +SERVICE = test_GSL +SERVICE_NAME = C++ Core Guideline Support Library Tests + +# Your service parts +FILES = service.cpp + +LOCAL_INCLUDES=-I$(PWD)/../../mod/GSL/include -I$(PWD)/../lest/include/lest + +# Your disk image +DISK= + +# IncludeOS location +ifndef INCLUDEOS_INSTALL +INCLUDEOS_INSTALL=$(HOME)/IncludeOS_install +endif + +include $(INCLUDEOS_INSTALL)/Makeseed diff --git a/test/GSL/README.md b/test/GSL/README.md new file mode 100644 index 0000000000..da05daa283 --- /dev/null +++ b/test/GSL/README.md @@ -0,0 +1,7 @@ +# Test basics of C++ Core Guidelines Support Library +This just tests a fraction of GSL functionality, but it's here to just verify that basic GSL support is in place and working in IncludeOS. + +Sucess: Outputs SUCCESS if all tests pass +Fail: Panic if any test fails + +NOTE: This test uses the `lest` unit test framework as well for some tests. The result of those tests will also be reported. diff --git a/test/GSL/run.sh b/test/GSL/run.sh new file mode 100755 index 0000000000..923937b16d --- /dev/null +++ b/test/GSL/run.sh @@ -0,0 +1,2 @@ +#! /bin/bash +source ${INCLUDEOS_HOME-$HOME/IncludeOS_install}/etc/run.sh test_GSL.img diff --git a/test/GSL/service.cpp b/test/GSL/service.cpp new file mode 100644 index 0000000000..28478755ba --- /dev/null +++ b/test/GSL/service.cpp @@ -0,0 +1,234 @@ +// This file is a part of the IncludeOS unikernel - www.includeos.org +// +// Copyright 2015 Oslo and Akershus University College of Applied Sciences +// and Alfred Bratterud +// +// 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. + +/** + A very superficial test to verify that basic STL is working + This is useful when we mess with / replace STL implementations + +**/ + +#include + +#define GSL_THROW_ON_CONTRACT_VIOLATION +#include +#include +#include +#include + +int clock_gettime(clockid_t clk_id, struct timespec *tp){ + (void*)clk_id; + (void*)tp; + return 0; +}; + + +#define MYINFO(X,...) INFO("Test GSL",X,##__VA_ARGS__) + +static int divcount_ = 0; + +const lest::test test_basic_gsl[] = { + { + + SCENARIO ("Basic Expects / Ensures behave as expected") { + + GIVEN ("A simple division function, requiring positive integers") { + + class Math { + public: + static int div (int x, int y) { + + printf("Dividing %i by %i ", x, y); + Expects( y > 0 ); + + printf("Dividing %i by %i ", x, y); + + int prevcount_ = divcount_; + + auto result = x / y; + divcount_++; + + Ensures(result == x / y); + Ensures(divcount_ > 0); + Ensures(divcount_ == prevcount_ + 1); + + return result; + + } + }; + + WHEN ("y is zero") { + int y = 0; + + THEN ("Division by y throws") { + EXPECT_THROWS( Math::div(100, y) ); + } + + } + + WHEN ("y is positive") { + int y = 4; + + THEN ("Division by y doesn't throw") { + EXPECT_NO_THROW( Math::div(100, y) ); + } + + AND_THEN("Division gives the correct result"){ + EXPECT( Math::div(100,y) == 100 / y ); + } + } + + WHEN ("y is negative"){ + int y = -90; + + THEN ("Divsion by y throws") {} + EXPECT_THROWS( Math::div(100, y) ); + + AND_THEN("Division should have succeeded twice"){ + EXPECT(divcount_ == 2); + } + } + } + } + }, + + { + SCENARIO("We can use span to replace pointer-and-size interfaces") { + + GIVEN ("A (member) function with a span parameter") { + + class Mem { + public: + static unsigned int count(gsl::span chars){ + + int i = 0; + for ( auto c : chars ) { + printf("%c ", c); + i++; + } + + printf("\n"); + return i; + } + + + }; + + WHEN ("We pass a raw pointer") { + char* name = (char*)"Bjarne Stroustrup"; + + THEN("if we lie to the about our size, span can't save us from overflow / underflow") { + EXPECT( Mem::count({name, 100}) != std::string(name).size()); + EXPECT( Mem::count({name, 10}) != std::string(name).size()); + } + + AND_THEN("If caller keeps track of the size, it will still work"){ + EXPECT( Mem::count({name, 17}) == std::string(name).size()); + } + + } + + WHEN ("We use std::array") { + std::array my_array {{'G','S','L',' ','l','o', 'o', 'k', 's', ' ', 'n', 'i', 'c', 'e'}}; + THEN("we're perfectly safe"){ + EXPECT( Mem::count(my_array) == my_array.size() ); + } + } + + WHEN ("We use normal array") { + char str2 [] = {49, 50, 51, 52, 53}; + + THEN ("Span helps us avoid decay to pointer") { + EXPECT( Mem::count(str2) == sizeof(str2)); + } + + AND_THEN("Using a span we can't iterate too far") { + + gsl::span strspan{str2, 5}; + auto it = strspan.begin(); + for (; it!= strspan.end(); it++) + printf("'%c' ", *it); + + it++; + EXPECT_THROWS(printf("DANGER: '%c' \n", *it)); + + } + } + } + + GIVEN ("A (Bad) span-interface that doesn't do any bounds checking") { + + class Bad { + public: + static unsigned char eighth(gsl::span chars){ + return chars[8]; + } + }; + + WHEN ("we pass in sufficient data") { + char* character = (char*) "Bjarne \"Yoda\" Stroustrup leads the Jedi council with wisdom"; + THEN("you can access the elements of the span using the index operator"){ + EXPECT(Bad::eighth({character, 20}) == 'Y'); + } + + } + + WHEN ("we pass in too little data") { + char* character = (char*) "Yoda"; + THEN("span saves us from complete embarrasment") { + EXPECT_THROWS(Bad::eighth({character, 4})); + } + + } + + } + } + },{ + CASE ("Using std::regex on a span") { + + std::string my_string = "GET /asdf%C3%B8lkj/&%C3%A6%C3%B8asldkfpoij09j13osmdv HTTP/1.1\n"; + gsl::span my_span {my_string}; + + for (auto c : my_span) + printf("%c",c); + printf("\n"); + + std::regex re_get("GET .*\n"); + printf("Match: %i \n", std::regex_match(my_string, re_get)); + + /** + * WARNING: HORRIBLE HACK + * Unfortunately gsl::span is still not compatible with std::regex + * See: https://github.com/Microsoft/GSL/issues/271 + **/ + EXPECT( std::regex_match(my_span.data(), my_span.data() + my_span.size(), re_get) ); + + } + }, +}; + +void Service::start() +{ + MYINFO ("Starting LEST-tests"); + // Lest takes command line params as vector + auto failed = lest::run(test_basic_gsl, {"-p"}); + + assert(not failed); + + MYINFO("SUCCESS"); + + +} diff --git a/test/GSL/test.py b/test/GSL/test.py new file mode 100755 index 0000000000..1b330254fe --- /dev/null +++ b/test/GSL/test.py @@ -0,0 +1,7 @@ +#! /usr/bin/python + +import sys +sys.path.insert(0,"..") + +import vmrunner +vmrunner.vms[0].boot() diff --git a/test/GSL/test.sh b/test/GSL/test.sh new file mode 100755 index 0000000000..90b11cea18 --- /dev/null +++ b/test/GSL/test.sh @@ -0,0 +1,5 @@ +#!/bin/bash +source ../test_base + +make +start test_GSL.img diff --git a/test/GSL/vm.json b/test/GSL/vm.json new file mode 100644 index 0000000000..ca0d6ee531 --- /dev/null +++ b/test/GSL/vm.json @@ -0,0 +1 @@ +{"image" : "test_GSL.img" } diff --git a/test/IDE/Makefile b/test/IDE/Makefile index c79b067a1b..c1e4407e0c 100644 --- a/test/IDE/Makefile +++ b/test/IDE/Makefile @@ -3,132 +3,18 @@ ################################################# # The name of your service -SERVICE = Test_IDE +SERVICE = test_IDE +SERVICE_NAME = IncludeOS IDE test # Your service parts FILES = service.cpp +# Your disk image +DISK= + # IncludeOS location ifndef INCLUDEOS_INSTALL - INCLUDEOS_INSTALL=$(HOME)/IncludeOS_install +INCLUDEOS_INSTALL=$(HOME)/IncludeOS_install endif -# Shorter name -INSTALL = $(INCLUDEOS_INSTALL) - -# Compiler/Linker -################################################### - -OPTIONS = -Ofast -msse3 -Wall -Wextra -mstackrealign - -# External Libraries -################################################### -LIBC_OBJ = $(INSTALL)/newlib/libc.a -LIBG_OBJ = $(INSTALL)/newlib/libg.a -LIBM_OBJ = $(INSTALL)/newlib/libm.a - -LIBGCC = $(INSTALL)/libgcc/libgcc.a -LIBCXX = $(INSTALL)/libcxx/libc++.a $(INSTALL)/libcxx/libc++abi.a - - -INC_NEWLIB=$(INSTALL)/newlib/include -INC_LIBCXX=$(INSTALL)/libcxx/include - -DEBUG_OPTS = -ggdb3 -v - -CPP = clang++-3.6 -target i686-elf -LD = ld - -INCLUDES = -I$(INC_LIBCXX) -I$(INSTALL)/api/sys -I$(INC_NEWLIB) -I$(INSTALL)/api - -CAPABS_COMMON = -msse3 -mstackrealign # Needed for 16-byte stack alignment (SSE) - -all: CAPABS = $(CAPABS_COMMON) -O2 -debug: CAPABS = $(CAPABS_COMMON) -O0 -stripped: CAPABS = $(CAPABS_COMMON) -Oz - -WARNS = -Wall -Wextra #-pedantic -CPPOPTS = $(CAPABS) $(WARNS) -c -m32 -std=c++14 -fno-stack-protector $(INCLUDES) -D_LIBCPP_HAS_NO_THREADS=1 #-flto -fno-exceptions - -LDOPTS = -nostdlib -melf_i386 -N --script=$(INSTALL)/linker.ld -flto - - -# Objects -################################################### - -CRTBEGIN_OBJ = $(INSTALL)/crt/crtbegin.o -CRTEND_OBJ = $(INSTALL)/crt/crtend.o -CRTI_OBJ = $(INSTALL)/crt/crti.o -CRTN_OBJ = $(INSTALL)/crt/crtn.o - -# Full link list -OBJS = $(FILES:.cpp=.o) .service_name.o -LIBS = $(INSTALL)/os.a $(LIBCXX) $(INSTALL)/os.a $(LIBC_OBJ) $(LIBM_OBJ) $(LIBGCC) - -OS_PRE = $(CRTBEGIN_OBJ) $(CRTI_OBJ) -OS_POST = $(CRTEND_OBJ) $(CRTN_OBJ) - -DEPS = $(OBJS:.o=.d) - -# Complete bulid -################################################### -# A complete build includes: -# - a "service", to be linked with OS-objects (OS included) - -all: service - -stripped: LDOPTS += -S #strip all -stripped: CPPOPTS += -Oz -stripped: service - - -# The same, but with debugging symbols (OBS: Dramatically increases binary size) -debug: CCOPTS += $(DEBUG_OPTS) -debug: CPPOPTS += $(DEBUG_OPTS) -debug: LDOPTS += -M --verbose - -debug: OBJS += $(LIBG_OBJ) - -debug: service #Don't wanna call 'all', since it strips debug info - -# Service -################################################### -service.o: service.cpp - @echo "\n>> Compiling the service" - $(CPP) $(CPPOPTS) -o $@ $< - -.service_name.o: $(INSTALL)/service_name.cpp - $(CPP) $(CPPOPTS) -DSERVICE_NAME="\"$(SERVICE)\"" -o $@ $< - -# Link the service with the os -service: $(OBJS) $(LIBS) - @echo "\n>> Linking service with OS" - $(LD) $(LDOPTS) $(OS_PRE) $(OBJS) $(LIBS) $(OS_POST) -o $(SERVICE) - @echo "\n>> Building image " $(SERVICE).img - $(INSTALL)/vmbuild $(INSTALL)/bootloader $(SERVICE) - -# Object files -################################################### - -# Runtime -crt%.o: $(INSTALL)/crt/crt%.s - @echo "\n>> Assembling C runtime:" $@ - $(CPP) $(CPPOPTS) -x assembler-with-cpp $< - -# General C++-files to object files -%.o: %.cpp - @echo "\n>> Compiling OS object without header" - $(CPP) $(CPPOPTS) -o $@ $< - -# AS-assembled object files -%.o: %.s - @echo "\n>> Assembling GNU 'as' files" - $(CPP) $(CPPOPTS) -x assembler-with-cpp $< - -# Cleanup -################################################### -clean: - $(RM) $(OBJS) $(DEPS) $(SERVICE) - $(RM) $(SERVICE).img - --include $(DEPS) +include $(INCLUDEOS_INSTALL)/Makeseed diff --git a/test/IDE/service.cpp b/test/IDE/service.cpp index e607b2acb8..658c5239f4 100644 --- a/test/IDE/service.cpp +++ b/test/IDE/service.cpp @@ -25,7 +25,7 @@ void Service::start() { printf("TESTING Disks\n\n"); - auto ide = hw::Dev::disk<0, hw::IDE>(); + auto ide = hw::Dev::disk<0, hw::IDE>(hw::IDE::MASTER); printf("Name : %s\n", ide.name()); printf("Size : %llu\n\n", ide.size()); @@ -36,12 +36,25 @@ void Service::start() printf("MAGIC sig: 0x%x\n\n", mbr->magic); ide.read(0, 3, [] (hw::IDE::buffer_t data) { - static int i = 0; - uint8_t* buf = (uint8_t*)data.get(); - printf("Async read, Block %d:\n", i); - for (int i = 0; i < 512; i++) - printf("%x ", buf[i]); - printf("\n"); - i++; - }); + static int i = 0; + uint8_t* buf = (uint8_t*)data.get(); + printf("Async read, Block %d:\n", i); + for (int i = 0; i < 512; i++) + printf("%x ", buf[i]); + printf("\n"); + i++; + }); + + printf("Reading sync:\n"); + mbr = (fs::MBR::mbr*)ide.read_sync(0).get(); + printf("Name: %.8s\n", mbr->oem_name); + printf("MAGIC sig: 0x%x\n\n", mbr->magic); + + ide.read(4, [] (hw::IDE::buffer_t data) { + uint8_t* buf = (uint8_t*)data.get(); + printf("Async read, Block %d:\n", 4); + for (int i = 0; i < 512; i++) + printf("%x ", buf[i]); + printf("\n"); + }); } diff --git a/test/IRQ_PIC/README.md b/test/IRQ_PIC/README.md index d41d3536df..878c6eb7eb 100644 --- a/test/IRQ_PIC/README.md +++ b/test/IRQ_PIC/README.md @@ -1,5 +1,16 @@ # Test basic PIC / IRQ functionality -For now we test that IRQ subscription works by soft-triggering interrupts. There's also a UDP server set up, making it possible to test IRQ 11 (currently) and e.g. keyboard IRQ 1 in connection with eachother. +Test of the following: + +1. Soft-triggering IRQ's will call interrupt-handlers, and in turn delegates +2. Serial port, IRQ 4: the service will read from serial port. With Qemu `-nographic` this will automatically be connected to stdin. Any character written to the serial port should result in a checkbox'ed receipt e.g. `[x] Serial port (IRQ 4) received 'a'` +3. UDP, IRQ 11: There's a UDP server set up, making it possible to test IRQ 11. Any data written to IP 10.0.0.42, UDP port 4242, should result in a checkbox'ed receipt, e.g. +``` +$ echo "asdf" | nc -u 10.0.0.42 4242 +-> [x] UDP received: 'asdf' +``` +4. A 1-second interval timer is continously running (IRQ 0). Each second it should output a chekced receipt with an incremented counter. + +For this setup it's expected that no combination of IRQ's should prevent other IRQ's from triggering. No automation yet. diff --git a/test/IRQ_PIC/service.cpp b/test/IRQ_PIC/service.cpp index 310062f86b..a0b33a05b2 100644 --- a/test/IRQ_PIC/service.cpp +++ b/test/IRQ_PIC/service.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -34,15 +35,17 @@ std::unique_ptr > inet; We're going to use the network- and keyboard interrupts for now, using UDP to trigger the NIC irq. - **/ +**/ void Service::start() { - auto& time = hw::PIT::instance(); + // Serial + auto& com1 = hw::Serial::port<1>(); - // Write something in half a second + // Timers + auto& time = hw::PIT::instance(); - // Assign an IP-address, using HĂ„rek-mapping :-) + // Network auto& eth0 = hw::Dev::eth<0,VirtioNet>(); auto& mac = eth0.mac(); auto& inet = *new net::Inet4(eth0, // Device @@ -57,8 +60,9 @@ void Service::start() sock.onRead([] (UDP::Socket& conn, UDP::addr_t addr, UDP::port_t port, const char* data, int len) -> int { - printf("Getting UDP data from %s: %i: %s\n", - addr.str().c_str(), port, data); + auto str = std::string(data,len); + str[len -1] = 0; + CHECK(1,"UDP received '%s'", str.c_str()); // send the same thing right back! conn.sendto(addr, port, data, len); return 0; @@ -102,13 +106,41 @@ void Service::start() asm("int $48"); // Expect "unexpected IRQ" asm("int $49"); // Expect "unexpected IRQ" - // Halting could/should be used in this test - // asm("hlt"); + + com1.enable_interrupt(); + + + /** + A custom IRQ-handler for the serial port + It doesn't send eoi, but it should work anyway + since we're using auto-EOI-mode for IRQ < 8 (master) + */ + IRQ_manager::subscribe(4, [](){ + uint16_t serial_port1 = 0x3F8; + //IRQ_manager::eoi(4); + char byte = 0; + while (hw::inb(serial_port1 + 5) & 1) + byte = hw::inb(serial_port1); + + CHECK(1,"Serial port (IRQ 4) received '%c'", byte); + }); + + + /* + IRQ_manager::subscribe(11,[](){ + // Calling eoi here will turn the IRQ line on and loop forever. + IRQ_manager::eoi(11); + INFO("IRQ","Network IRQ\n"); + });*/ + // Enabling a timer causes freeze in debug mode, for some reason - time.onTimeout(1s, [](){ printf("One second passed...\n"); }); - time.onTimeout(2s, [](){ printf("Two seconds passed...\n"); }); - time.onTimeout(5s, [](){ printf("Five seconds passed...\n"); }); + time.onRepeatedTimeout(1s, [](){ + static int time_counter = 0; + CHECK(1,"Time %i", ++time_counter); + + }); + INFO("IRQ test","Expect IRQ subscribers to get called now "); } diff --git a/test/STL/Makefile b/test/STL/Makefile index 4037d03c72..542c71117b 100644 --- a/test/STL/Makefile +++ b/test/STL/Makefile @@ -3,13 +3,13 @@ ################################################# # The name of your service -SERVICE = Test_STL +SERVICE = test_STL SERVICE_NAME = IncludeOS basic STL test # Your service parts FILES = service.cpp -LOCAL_INCLUDES = $(PWD)/../lest/include/lest +LOCAL_INCLUDES=-I$(PWD)/../lest/include/lest # Your disk image DISK= diff --git a/test/STL/README.md b/test/STL/README.md new file mode 100644 index 0000000000..12c09bd023 --- /dev/null +++ b/test/STL/README.md @@ -0,0 +1,7 @@ +# Test basics of C++ Standard Template Library +This just tests a fraction of STL functionality, but it's here to just verify that basic STL support is in place and working. Useful i.e. when working on porting STL or switching implementation (e.g. to EASTL) + +Sucess: Outputs SUCCESS if all tests pass +Fail: Panic if any test fails + +NOTE: This test uses the `lest` unit test framework as well for some tests. The result of those tests will also be reported. diff --git a/test/STL/service.cpp b/test/STL/service.cpp index b64d21b0cd..85b27bafe5 100644 --- a/test/STL/service.cpp +++ b/test/STL/service.cpp @@ -19,7 +19,7 @@ A very superficial test to verify that basic STL is working This is useful when we mess with / replace STL implementations - **/ +**/ #include @@ -41,60 +41,60 @@ int clock_gettime(clockid_t clk_id, struct timespec *tp){ using namespace std; const lest::test specification[] = -{ { - SCENARIO( "vectors can be sized and resized" "[vector]" ) { + SCENARIO( "vectors can be sized and resized" "[vector]" ) + { - GIVEN( "A vector with some items" ) { - std::vector v( 5 ); - - EXPECT( v.size() == 5u ); - EXPECT( v.capacity() >= 5u ); + GIVEN( "A vector with some items" ) { + std::vector v( 5 ); - WHEN( "the size is increased" ) { - v.resize( 10 ); + EXPECT( v.size() == 5u ); + EXPECT( v.capacity() >= 5u ); - THEN( "the size and capacity change" ) { - EXPECT( v.size() == 10u); - EXPECT( v.capacity() >= 10u ); - } - } - WHEN( "the size is reduced" ) { - v.resize( 0 ); + WHEN( "the size is increased" ) { + v.resize( 10 ); - THEN( "the size changes but not capacity" ) { - EXPECT( v.size() == 0u ); - EXPECT( v.capacity() >= 5u ); + THEN( "the size and capacity change" ) { + EXPECT( v.size() == 10u); + EXPECT( v.capacity() >= 10u ); + } } - } - WHEN( "more capacity is reserved" ) { - v.reserve( 10 ); + WHEN( "the size is reduced" ) { + v.resize( 0 ); - THEN( "the capacity changes but not the size" ) { - EXPECT( v.size() == 5u ); - EXPECT( v.capacity() >= 10u ); + THEN( "the size changes but not capacity" ) { + EXPECT( v.size() == 0u ); + EXPECT( v.capacity() >= 5u ); + } } - WHEN( "less capacity is reserved again" ) { - v.reserve( 7 ); + WHEN( "more capacity is reserved" ) { + v.reserve( 10 ); - THEN( "capacity remains unchanged" ) { + THEN( "the capacity changes but not the size" ) { + EXPECT( v.size() == 5u ); EXPECT( v.capacity() >= 10u ); } + WHEN( "less capacity is reserved again" ) { + v.reserve( 7 ); + + THEN( "capacity remains unchanged" ) { + EXPECT( v.capacity() >= 10u ); + } + } } - } - WHEN( "less capacity is reserved" ) { - v.reserve( 0 ); + WHEN( "less capacity is reserved" ) { + v.reserve( 0 ); - THEN( "neither size nor capacity are changed" ) { - EXPECT( v.size() == 5u ); - EXPECT( v.capacity() >= 5u ); + THEN( "neither size nor capacity are changed" ) { + EXPECT( v.size() == 5u ); + EXPECT( v.capacity() >= 5u ); + } } } } } - } -}; + }; #define MYINFO(X,...) INFO("Test STL",X,##__VA_ARGS__) diff --git a/test/STL/test.py b/test/STL/test.py new file mode 100644 index 0000000000..b07ca7dfa9 --- /dev/null +++ b/test/STL/test.py @@ -0,0 +1,5 @@ +import sys +sys.path.insert(0,"..") + +import vmrunner +vmrunner.vms[0].boot() diff --git a/test/STL/test.sh b/test/STL/test.sh index 0ec44520d7..14e5d77c81 100755 --- a/test/STL/test.sh +++ b/test/STL/test.sh @@ -1,5 +1,5 @@ #!/bin/bash source ../test_base -make SERVICE=Test_STL FILES=service.cpp -start Test_STL.img "Basic STL test" +make +start test_STL.img diff --git a/test/STL/vm.json b/test/STL/vm.json new file mode 100644 index 0000000000..6fb9c2fa3c --- /dev/null +++ b/test/STL/vm.json @@ -0,0 +1 @@ +{"image" : "test_STL.img" } diff --git a/test/STREAM/Makefile b/test/STREAM/Makefile index 94d46d692b..0b039e572b 100644 --- a/test/STREAM/Makefile +++ b/test/STREAM/Makefile @@ -8,6 +8,7 @@ SERVICE_NAME = STREAM Memory benchmark # Your service parts FILES = service.cpp stream.cpp + # Your disk image DISK= diff --git a/test/STREAM/run.sh b/test/STREAM/run.sh index 3b2132d2cb..961760823a 100755 --- a/test/STREAM/run.sh +++ b/test/STREAM/run.sh @@ -1,3 +1,3 @@ #! /bin/bash -source ${INCLUDEOS_HOME-$HOME/IncludeOS_install}/etc/run.sh +MEM="-m 256" source ${INCLUDEOS_HOME-$HOME/IncludeOS_install}/etc/run.sh diff --git a/test/UDP/Makefile b/test/UDP/Makefile index 463b31fa1c..ddce4f3bb1 100644 --- a/test/UDP/Makefile +++ b/test/UDP/Makefile @@ -3,11 +3,12 @@ ################################################# # The name of your service -SERVICE = "Test_UDP" -SERVICE_NAME = "IncludeOS\ UDP\ test" +SERVICE = test_udp +SERVICE_NAME = IncludeOS UDP test # Your service parts FILES = service.cpp + # Your disk image DISK= diff --git a/test/UDP/service.cpp b/test/UDP/service.cpp index f789db5da5..2c3a9046b9 100644 --- a/test/UDP/service.cpp +++ b/test/UDP/service.cpp @@ -31,27 +31,29 @@ void Service::start() { // Assign an IP-address, using HĂ„rek-mapping :-) auto& eth0 = hw::Dev::eth<0,VirtioNet>(); - auto& mac = eth0.mac(); - auto& inet = *new net::Inet4(eth0, // Device - {{ mac.part[2],mac.part[3],mac.part[4],mac.part[5] }}, // IP - {{ 255,255,0,0 }} ); // Netmask + { 10,0,0,42 }, // IP + { 255,255,0,0 } ); // Netmask - printf("Service IP address: %s \n", inet.ip_addr().str().c_str()); + printf("Service IP address is %s\n", inet.ip_addr().str().c_str()); // UDP - UDP::port_t port = 4242; + const UDP::port_t port = 4242; auto& sock = inet.udp().bind(port); - sock.onRead([] (UDP::Socket& conn, UDP::addr_t addr, UDP::port_t port, const char* data, int len) -> int - { - printf("Getting UDP data from %s: %i: %s\n", - addr.str().c_str(), port, data); - // send the same thing right back! - conn.sendto(addr, port, data, len); - return 0; - }); - - printf("UDP server listening to port %i \n",port); - + sock.on_read( + [&sock] (UDP::addr_t addr, UDP::port_t port, + const char* data, size_t len) + { + std::string strdata(data, len); + CHECK(1, "Getting UDP data from %s: %d -> %s", + addr.str().c_str(), port, strdata.c_str()); + // send the same thing right back! + sock.sendto(addr, port, data, len, + [] { + INFO("UDP test", "SUCCESS"); + }); + }); + + INFO("UDP test", "Listening on port %d\n", port); } diff --git a/test/UDP/test.py b/test/UDP/test.py new file mode 100755 index 0000000000..4f79da1b60 --- /dev/null +++ b/test/UDP/test.py @@ -0,0 +1,38 @@ +#! /usr/bin/python +import sys +sys.path.insert(0,"..") + +import vmrunner +import socket + + +def UDP_test(): + print " Performing UDP tests" + HOST, PORT = "10.0.0.42", 4242 + sock = socket.socket + # SOCK_DGRAM is the socket type to use for UDP sockets + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + + data = "Douche" + sock.sendto(data+"\n", (HOST, PORT)) + received = sock.recv(1024) + + print " Sent: {}".format(data) + print " Received: {}".format(received) + + data = "Bag" + sock.sendto(data+"\n", (HOST, PORT)) + received = sock.recv(1024) + + print " Sent: {}".format(data) + print " Received: {}".format(received) + + +# Get an auto-created VM from the vmrunner +vm = vmrunner.vms[0] + +# Add custom event-handler +vm.on_output("IncludeOS UDP test", UDP_test) + +# Boot the VM, taking a timeout as parameter +vm.boot(20) diff --git a/test/UDP/vm.json b/test/UDP/vm.json new file mode 100644 index 0000000000..24576f36b3 --- /dev/null +++ b/test/UDP/vm.json @@ -0,0 +1,6 @@ +{ + "image" : "test_udp.img", + "net" : [{"type" : "virtio", "mac" : "c0:01:0a:00:00:2a"}], + "cpu" : "host", + "mem" : 256 +} diff --git a/test/bufstore/Makefile b/test/bufstore/Makefile new file mode 100644 index 0000000000..6fd9a57702 --- /dev/null +++ b/test/bufstore/Makefile @@ -0,0 +1,22 @@ +################################################# +# IncludeOS SERVICE makefile # +################################################# + +# The name of your service +SERVICE = test_bufstore +SERVICE_NAME = net::buffer_store tests + +# Your service parts +FILES = service.cpp + +# Your disk image +DISK= + + + +# IncludeOS location +ifndef INCLUDEOS_INSTALL +INCLUDEOS_INSTALL=$(HOME)/IncludeOS_install +endif + +include $(INCLUDEOS_INSTALL)/Makeseed diff --git a/test/bufstore/README.md b/test/bufstore/README.md new file mode 100644 index 0000000000..95e7b422f9 --- /dev/null +++ b/test/bufstore/README.md @@ -0,0 +1,3 @@ +# Test net::BufferStore and net::Packet chaining + +Internal tests that verifies that packet chaining plays well with the buffer store, i.e. that you can chain lots of packets, dechain, and they all return their buffers back to the bufstore. diff --git a/test/bufstore/run.sh b/test/bufstore/run.sh new file mode 100755 index 0000000000..3b2132d2cb --- /dev/null +++ b/test/bufstore/run.sh @@ -0,0 +1,3 @@ +#! /bin/bash +source ${INCLUDEOS_HOME-$HOME/IncludeOS_install}/etc/run.sh + diff --git a/test/bufstore/service.cpp b/test/bufstore/service.cpp new file mode 100644 index 0000000000..9b4e280945 --- /dev/null +++ b/test/bufstore/service.cpp @@ -0,0 +1,100 @@ +// This file is a part of the IncludeOS unikernel - www.includeos.org +// +// Copyright 2015 Oslo and Akershus University College of Applied Sciences +// and Alfred Bratterud +// +// 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 DEBUG // Debug supression + +#include +#include +#include + +using namespace std; +using namespace net; + +constexpr size_t bufcount_ {100}; +BufferStore bufstore_{ bufcount_, 1500, 10 }; + +void Service::start() +{ + + INFO("Test net::Packet","Starting tests"); + + INFO("Test 1","Naively create, chain and release packets"); + + // Create a release delegate - i.e. the thing in charge of releasing packets + auto release = BufferStore::release_del::from + (bufstore_); + + // Create packets, using buffer from the bufstore, and the bufstore's release + auto packet = std::make_shared(bufstore_.get_offset_buffer(), + bufstore_.offset_bufsize(), 1500, release); + + CHECKSERT(bufstore_.buffers_available() == bufcount_ - 1, "Bufcount is now %i", bufcount_ -1); + + int chain_size = bufcount_; + + // Chain packets + for (int i = 0; i < chain_size - 1; i++){ + auto chained_packet = std::make_shared(bufstore_.get_offset_buffer(), + bufstore_.offset_bufsize(), 1500, release); + packet->chain(chained_packet); + CHECKSERT(bufstore_.buffers_available() == bufcount_ - i - 2 , "Bufcount is now %i", bufcount_ - i -2); + } + + + // Release + INFO("Test 1","Releaseing packet-chain all at once: Expect bufcount restored"); + packet = 0; + CHECKSERT(bufstore_.buffers_available() == bufcount_ , "Bufcount is now %i", bufcount_); + + INFO("Test 2","Create and chain packets, release one-by-one"); + + // Reinitialize the first packet + packet = std::make_shared(bufstore_.get_offset_buffer(), + bufstore_.offset_bufsize(), 1500, release); + + CHECKSERT(bufstore_.buffers_available() == bufcount_ - 1, "Bufcount is now %i", bufcount_ -1); + + // Chain + for (int i = 0; i < chain_size - 1; i++){ + auto chained_packet = std::make_shared(bufstore_.get_offset_buffer(), + bufstore_.offset_bufsize(), 1500, release); + packet->chain(chained_packet); + CHECKSERT(bufstore_.buffers_available() == bufcount_ - i -2, "Bufcount is now %i", bufcount_ - i -2); + } + + INFO("Test 2","Releasing packet-chain one-by-one"); + + // Release one-by-one + auto tail = packet; + size_t i = 0; + while(tail && i < bufcount_ - 1 ) { + tail = tail->detach_tail(); + CHECKSERT(bufstore_.buffers_available() == i, + "Bufcount is now %i == %i", i, + bufstore_.buffers_available()); + i++; + } + + INFO("Test 2","Releasing last packet"); + tail = 0; + packet = 0; + CHECKSERT(bufstore_.buffers_available() == bufcount_ , "Bufcount is now %i", bufcount_); + + + INFO("Tests","SUCCESS"); + +} diff --git a/test/bufstore/test.sh b/test/bufstore/test.sh new file mode 100755 index 0000000000..be950c23ec --- /dev/null +++ b/test/bufstore/test.sh @@ -0,0 +1,5 @@ +#!/bin/bash +source ../test_base + +make +start test_bufstore.img "Test bufstore" diff --git a/test/cmos/Makefile b/test/cmos/Makefile new file mode 100644 index 0000000000..14defe56c6 --- /dev/null +++ b/test/cmos/Makefile @@ -0,0 +1,20 @@ +################################################# +# IncludeOS SERVICE makefile # +################################################# + +# The name of your service +SERVICE = test_cmos +SERVICE_NAME = CMOS tests + +# Your service parts +FILES = service.cpp +# Your disk image +DISK= +LOCAL_INCLUDES= + +# IncludeOS location +ifndef INCLUDEOS_INSTALL +INCLUDEOS_INSTALL=$(HOME)/IncludeOS_install +endif + +include $(INCLUDEOS_INSTALL)/Makeseed diff --git a/test/cmos/README.md b/test/cmos/README.md new file mode 100644 index 0000000000..00e32646f9 --- /dev/null +++ b/test/cmos/README.md @@ -0,0 +1,7 @@ +# Test C runtime +Essentially this tests global constructors, which needs crti.o, crtn.o etc. to be linked in properly to work. + +Internal test, self-explanatory output. + +Success: Outputs SUCCESS if all tests pass +Fail: Panic on any failed tests diff --git a/test/cmos/run.sh b/test/cmos/run.sh new file mode 100755 index 0000000000..b49e3b012f --- /dev/null +++ b/test/cmos/run.sh @@ -0,0 +1,2 @@ +#! /bin/bash +source ${INCLUDEOS_HOME-$HOME/IncludeOS_install}/etc/run.sh test_cmos.img diff --git a/test/cmos/service.cpp b/test/cmos/service.cpp new file mode 100644 index 0000000000..475f5e8c4b --- /dev/null +++ b/test/cmos/service.cpp @@ -0,0 +1,96 @@ +// This file is a part of the IncludeOS unikernel - www.includeos.org +// +// Copyright 2015 Oslo and Akershus University College of Applied Sciences +// and Alfred Bratterud +// +// 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. + +#include +#include + +#include +#include + + +using namespace std::chrono; + +void Service::start() +{ + + INFO("Test CMOS","Testing C runtime \n"); + + CHECKSERT(1 == 1, "CMOS exists"); + + unsigned short total; + unsigned char lowmem, highmem; + + lowmem = cmos::get(0x30); + highmem = cmos::get(0x31); + + total = lowmem | highmem << 8; + + printf("Total memory: %i \n", total); + + auto regB = cmos::get(cmos::r_status_b); + printf("RegB: 0x%x Binary mode/daylight: %i, 12-hr-mode: %i \n", + regB, + regB & cmos::b_daylight_savings_enabled, + regB & cmos::b_24_hr_clock); + + cmos::set(cmos::r_status_b, cmos::b_24_hr_clock | cmos::b_binary_mode); + + regB = cmos::get(cmos::r_status_b); + printf("RegB: 0x%x Binary mode/daylight: %i, 12-hr-mode: %i \n", + regB, + regB & cmos::b_daylight_savings_enabled, + regB & cmos::b_24_hr_clock); + + uint32_t wraps = 0; + uint32_t i = 0; + + while (!cmos::update_in_progress()) { + i++; + if (i == 0) { + wraps++; + printf("Wrapped %i times, still no update \n", wraps); + std::cout << cmos::now().to_string() << "\n"; + if (wraps > 10) + panic("CMOS time didn't update after 10 * 2^32 increments."); + } + }; + + CHECKSERT(1, "CMOS updated"); + + auto tsc_base1 = OS::cycles_since_boot(); + + hw::PIT::instance().onRepeatedTimeout(1s, [tsc_base1](){ + static auto tsc_base = tsc_base1; + uint64_t ticks_pr_sec = OS::cycles_since_boot() - tsc_base; + auto tsc1 = OS::cycles_since_boot(); + auto rtc1 = cmos::now(); + auto tsc2 = OS::cycles_since_boot(); + auto tsc3 = OS::cycles_since_boot(); + + printf(" Cycles last sec: %llu \n", ticks_pr_sec); + printf(" Reading CMOS Wall-clock took: %llu cycles \n", tsc2 - tsc1); + printf(" RDTSC took: %llu cycles \n", tsc3 - tsc2); + + printf("\n"); + printf("Internet timestamp: %s\n",rtc1.to_string().c_str()); + printf("Seconds since Epoch: %i\n",rtc1.to_epoch()); + printf("Day of year: %i\n", rtc1.day_of_year()); + printf("-------------------------------- \n\n"); + tsc_base = OS::cycles_since_boot(); + + }); +} diff --git a/test/crt/Makefile b/test/crt/Makefile index f8578bbedb..fb4fbda02d 100644 --- a/test/crt/Makefile +++ b/test/crt/Makefile @@ -3,132 +3,18 @@ ################################################# # The name of your service -SERVICE = Test_C_Runtime +SERVICE = test_crt +SERVICE_NAME = C runtime test # Your service parts FILES = service.cpp +# Your disk image +DISK= +LOCAL_INCLUDES= # IncludeOS location ifndef INCLUDEOS_INSTALL - INCLUDEOS_INSTALL=$(HOME)/IncludeOS_install +INCLUDEOS_INSTALL=$(HOME)/IncludeOS_install endif -# Shorter name -INSTALL = $(INCLUDEOS_INSTALL) - -# Compiler/Linker -################################################### - -OPTIONS = -Ofast -msse3 -Wall -Wextra -mstackrealign - -# External Libraries -################################################### -LIBC_OBJ = $(INSTALL)/newlib/libc.a -LIBG_OBJ = $(INSTALL)/newlib/libg.a -LIBM_OBJ = $(INSTALL)/newlib/libm.a - -LIBGCC = $(INSTALL)/libgcc/libgcc.a -LIBCXX = $(INSTALL)/libcxx/libc++.a $(INSTALL)/libcxx/libc++abi.a - - -INC_NEWLIB=$(INSTALL)/newlib/include -INC_LIBCXX=$(INSTALL)/libcxx/include - -DEBUG_OPTS = -ggdb3 -v - -CPP = clang++-3.6 -target i686-elf -LD = ld - -INCLUDES = -I$(INC_LIBCXX) -I$(INSTALL)/api/sys -I$(INC_NEWLIB) -I$(INSTALL)/api - -CAPABS_COMMON = -msse3 -mstackrealign # Needed for 16-byte stack alignment (SSE) - -all: CAPABS = $(CAPABS_COMMON) -O2 -debug: CAPABS = $(CAPABS_COMMON) -O0 -stripped: CAPABS = $(CAPABS_COMMON) -Oz - -WARNS = -Wall -Wextra #-pedantic -CPPOPTS = $(CAPABS) $(WARNS) -c -m32 -std=c++14 -fno-stack-protector $(INCLUDES) -D_LIBCPP_HAS_NO_THREADS=1 #-flto -fno-exceptions - -LDOPTS = -nostdlib -melf_i386 -N --script=$(INSTALL)/linker.ld -flto - - -# Objects -################################################### - -CRTBEGIN_OBJ = $(INSTALL)/crt/crtbegin.o -CRTEND_OBJ = $(INSTALL)/crt/crtend.o -CRTI_OBJ = $(INSTALL)/crt/crti.o -CRTN_OBJ = $(INSTALL)/crt/crtn.o - -# Full link list -OBJS = $(FILES:.cpp=.o) .service_name.o -LIBS = $(INSTALL)/os.a $(LIBCXX) $(INSTALL)/os.a $(LIBC_OBJ) $(LIBM_OBJ) $(LIBGCC) - -OS_PRE = $(CRTBEGIN_OBJ) $(CRTI_OBJ) -OS_POST = $(CRTEND_OBJ) $(CRTN_OBJ) - -DEPS = $(OBJS:.o=.d) - -# Complete bulid -################################################### -# A complete build includes: -# - a "service", to be linked with OS-objects (OS included) - -all: service - -stripped: LDOPTS += -S #strip all -stripped: CPPOPTS += -Oz -stripped: service - - -# The same, but with debugging symbols (OBS: Dramatically increases binary size) -debug: CCOPTS += $(DEBUG_OPTS) -debug: CPPOPTS += $(DEBUG_OPTS) -debug: LDOPTS += -M --verbose - -debug: OBJS += $(LIBG_OBJ) - -debug: service #Don't wanna call 'all', since it strips debug info - -# Service -################################################### -service.o: service.cpp - @echo "\n>> Compiling the service" - $(CPP) $(CPPOPTS) -o $@ $< - -.service_name.o: $(INSTALL)/service_name.cpp - $(CPP) $(CPPOPTS) -DSERVICE_NAME="\"$(SERVICE)\"" -o $@ $< - -# Link the service with the os -service: $(OBJS) $(LIBS) - @echo "\n>> Linking service with OS" - $(LD) $(LDOPTS) $(OS_PRE) $(OBJS) $(LIBS) $(OS_POST) -o $(SERVICE) - @echo "\n>> Building image " $(SERVICE).img - $(INSTALL)/vmbuild $(INSTALL)/bootloader $(SERVICE) - -# Object files -################################################### - -# Runtime -crt%.o: $(INSTALL)/crt/crt%.s - @echo "\n>> Assembling C runtime:" $@ - $(CPP) $(CPPOPTS) -x assembler-with-cpp $< - -# General C++-files to object files -%.o: %.cpp - @echo "\n>> Compiling OS object without header" - $(CPP) $(CPPOPTS) -o $@ $< - -# AS-assembled object files -%.o: %.s - @echo "\n>> Assembling GNU 'as' files" - $(CPP) $(CPPOPTS) -x assembler-with-cpp $< - -# Cleanup -################################################### -clean: - $(RM) $(OBJS) $(DEPS) $(SERVICE) - $(RM) $(SERVICE).img - --include $(DEPS) +include $(INCLUDEOS_INSTALL)/Makeseed diff --git a/test/crt/README.md b/test/crt/README.md new file mode 100644 index 0000000000..00e32646f9 --- /dev/null +++ b/test/crt/README.md @@ -0,0 +1,7 @@ +# Test C runtime +Essentially this tests global constructors, which needs crti.o, crtn.o etc. to be linked in properly to work. + +Internal test, self-explanatory output. + +Success: Outputs SUCCESS if all tests pass +Fail: Panic on any failed tests diff --git a/test/crt/debug/service.gdb b/test/crt/debug/service.gdb deleted file mode 100644 index f5acb4ff3c..0000000000 --- a/test/crt/debug/service.gdb +++ /dev/null @@ -1,5 +0,0 @@ -file service -break _start -break OS::start -set non-stop off -target remote localhost:1234 \ No newline at end of file diff --git a/test/crt/service.cpp b/test/crt/service.cpp index c4f0d0f99a..7459772e22 100644 --- a/test/crt/service.cpp +++ b/test/crt/service.cpp @@ -6,9 +6,9 @@ // 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. @@ -16,24 +16,25 @@ // limitations under the License. #include +#include class global { static int i; public: global(){ - printf("[*] Global constructor printing %i \n",++i); + CHECK(1,"Global constructor printing %i",++i); } - + void test(){ - printf("[*] C++ constructor finds %i instances \n",i); + CHECK(1,"C++ constructor finds %i instances",i); } - + int instances(){ return i; } - + ~global(){ - printf("[*] C++ destructor deleted 1 instance, %i remains \n",--i); + CHECK(1,"C++ destructor deleted 1 instance, %i remains",--i); } - + }; @@ -53,25 +54,20 @@ __attribute__ ((constructor)) void foo(void) void Service::start() { - - printf("TESTING C runtime \n"); - - printf("[%s] Global C constructors in service \n", - _test_glob3 == 0xfa7ca7 ? "x" : " "); - - printf("[%s] Global int initialization in service \n", - _test_glob2 == 1 ? "x" : " "); - - + + INFO("Test CRT","Testing C runtime \n"); + + CHECKSERT(_test_glob3 == 0xfa7ca7, "Global C constructors in service"); + CHECKSERT(_test_glob2 == 1, "Global int initialization in service"); + global* glob2 = new global();; glob1.test(); - printf("[%s] Local C++ constructors in service \n", glob1.instances() == 2 ? "x" : " "); + CHECKSERT(glob1.instances() == 2, "Local C++ constructors in service"); - delete glob2; - printf("[%s] C++ destructors in service \n", glob1.instances() == 1 ? "x" : " "); - - - - + CHECKSERT(glob1.instances() == 1, "C++ destructors in service"); + + + INFO("Test CRT", "SUCCESS"); + } diff --git a/test/exceptions/Makefile b/test/exceptions/Makefile index aa0ca134aa..f05714f6b7 100644 --- a/test/exceptions/Makefile +++ b/test/exceptions/Makefile @@ -3,132 +3,18 @@ ################################################# # The name of your service -SERVICE = Test_Exceptions +SERVICE = test_exceptions +SERVICE_NAME = Exceptions test # Your service parts FILES = service.cpp +# Your disk image +DISK= +LOCAL_INCLUDES= # IncludeOS location ifndef INCLUDEOS_INSTALL - INCLUDEOS_INSTALL=$(HOME)/IncludeOS_install +INCLUDEOS_INSTALL=$(HOME)/IncludeOS_install endif -# Shorter name -INSTALL = $(INCLUDEOS_INSTALL) - -# Compiler/Linker -################################################### - -OPTIONS = -Ofast -msse3 -Wall -Wextra -mstackrealign - -# External Libraries -################################################### -LIBC_OBJ = $(INSTALL)/newlib/libc.a -LIBG_OBJ = $(INSTALL)/newlib/libg.a -LIBM_OBJ = $(INSTALL)/newlib/libm.a - -LIBGCC = $(INSTALL)/libgcc/libgcc.a -LIBCXX = $(INSTALL)/libcxx/libc++.a $(INSTALL)/libcxx/libc++abi.a - - -INC_NEWLIB=$(INSTALL)/newlib/include -INC_LIBCXX=$(INSTALL)/libcxx/include - -DEBUG_OPTS = -ggdb3 -v - -CPP = clang++-3.6 -target i686-elf -LD = ld - -INCLUDES = -I$(INC_LIBCXX) -I$(INSTALL)/api/sys -I$(INC_NEWLIB) -I$(INSTALL)/api - -CAPABS_COMMON = -msse3 -mstackrealign # Needed for 16-byte stack alignment (SSE) - -all: CAPABS = $(CAPABS_COMMON) -O2 -debug: CAPABS = $(CAPABS_COMMON) -O0 -stripped: CAPABS = $(CAPABS_COMMON) -Oz - -WARNS = -Wall -Wextra #-pedantic -CPPOPTS = $(CAPABS) $(WARNS) -c -m32 -std=c++14 -fno-stack-protector $(INCLUDES) -D_LIBCPP_HAS_NO_THREADS=1 #-flto -fno-exceptions - -LDOPTS = -nostdlib -melf_i386 -N --script=$(INSTALL)/linker.ld -flto - - -# Objects -################################################### - -CRTBEGIN_OBJ = $(INSTALL)/crt/crtbegin.o -CRTEND_OBJ = $(INSTALL)/crt/crtend.o -CRTI_OBJ = $(INSTALL)/crt/crti.o -CRTN_OBJ = $(INSTALL)/crt/crtn.o - -# Full link list -OBJS = $(FILES:.cpp=.o) .service_name.o -LIBS = $(INSTALL)/os.a $(LIBCXX) $(INSTALL)/os.a $(LIBC_OBJ) $(LIBM_OBJ) $(LIBGCC) - -OS_PRE = $(CRTBEGIN_OBJ) $(CRTI_OBJ) -OS_POST = $(CRTEND_OBJ) $(CRTN_OBJ) - -DEPS = $(OBJS:.o=.d) - -# Complete bulid -################################################### -# A complete build includes: -# - a "service", to be linked with OS-objects (OS included) - -all: service - -stripped: LDOPTS += -S #strip all -stripped: CPPOPTS += -Oz -stripped: service - - -# The same, but with debugging symbols (OBS: Dramatically increases binary size) -debug: CCOPTS += $(DEBUG_OPTS) -debug: CPPOPTS += $(DEBUG_OPTS) -debug: LDOPTS += -M --verbose - -debug: OBJS += $(LIBG_OBJ) - -debug: service #Don't wanna call 'all', since it strips debug info - -# Service -################################################### -service.o: service.cpp - @echo "\n>> Compiling the service" - $(CPP) $(CPPOPTS) -o $@ $< - -.service_name.o: $(INSTALL)/service_name.cpp - $(CPP) $(CPPOPTS) -DSERVICE_NAME="\"$(SERVICE)\"" -o $@ $< - -# Link the service with the os -service: $(OBJS) $(LIBS) - @echo "\n>> Linking service with OS" - $(LD) $(LDOPTS) $(OS_PRE) $(OBJS) $(LIBS) $(OS_POST) -o $(SERVICE) - @echo "\n>> Building image " $(SERVICE).img - $(INSTALL)/vmbuild $(INSTALL)/bootloader $(SERVICE) - -# Object files -################################################### - -# Runtime -crt%.o: $(INSTALL)/crt/crt%.s - @echo "\n>> Assembling C runtime:" $@ - $(CPP) $(CPPOPTS) -x assembler-with-cpp $< - -# General C++-files to object files -%.o: %.cpp - @echo "\n>> Compiling OS object without header" - $(CPP) $(CPPOPTS) -o $@ $< - -# AS-assembled object files -%.o: %.s - @echo "\n>> Assembling GNU 'as' files" - $(CPP) $(CPPOPTS) -x assembler-with-cpp $< - -# Cleanup -################################################### -clean: - $(RM) $(OBJS) $(DEPS) $(SERVICE) - $(RM) $(SERVICE).img - --include $(DEPS) +include $(INCLUDEOS_INSTALL)/Makeseed diff --git a/test/exceptions/README.md b/test/exceptions/README.md new file mode 100644 index 0000000000..24b8991b42 --- /dev/null +++ b/test/exceptions/README.md @@ -0,0 +1,8 @@ +# Test exceptions + +Exceptions are usually taken for granted, but they require proper unwinding, which is not itself a part of the standard C++ library. + +These tests are internal only, so just running `test.sh` should be enough, and the output will be self explanatory. + +Sucess: Outputs SUCCESS if all tests pass +Fail: Panic if any test fails diff --git a/test/exceptions/debug/service.gdb b/test/exceptions/debug/service.gdb deleted file mode 100644 index f5acb4ff3c..0000000000 --- a/test/exceptions/debug/service.gdb +++ /dev/null @@ -1,5 +0,0 @@ -file service -break _start -break OS::start -set non-stop off -target remote localhost:1234 \ No newline at end of file diff --git a/test/exceptions/service.cpp b/test/exceptions/service.cpp index c8c67fe336..7995016eff 100644 --- a/test/exceptions/service.cpp +++ b/test/exceptions/service.cpp @@ -6,9 +6,9 @@ // 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. @@ -17,7 +17,7 @@ #include #include - +#include class CustomException : public std::runtime_error { using runtime_error::runtime_error; @@ -25,11 +25,11 @@ class CustomException : public std::runtime_error { void Service::start() { - + printf("TESTING Exceptions \n"); - + const char* error_msg = "Crazy Error!"; - + try { printf("[x] Inside try-block \n"); if (OS::uptime() > 0.1){ @@ -38,31 +38,31 @@ void Service::start() } }catch(std::runtime_error e){ - + printf("[%s] Caught runtime error: %s \n", std::string(e.what()) == std::string(error_msg) ? "x" : " " ,e.what()); - + }catch(...) { - + printf("[ ] Caught something - but not what we expected \n"); - + } - + std::string custom_msg = "Custom exceptions are useful"; - std::string cought_msg = ""; - + std::string caught_msg = ""; + try { // Trying to throw a custom exception throw CustomException(custom_msg); } catch (CustomException e){ - - cought_msg = e.what(); - + + caught_msg = e.what(); + } catch (...) { - + printf("[ ] Couldn't catch custom exception \n"); - + } - - assert(cought_msg == custom_msg); - printf("[x] Cought custom exception \n"); + + assert(caught_msg == custom_msg); + printf("[x] Caught custom exception \n"); } diff --git a/test/exceptions/test.sh b/test/exceptions/test.sh new file mode 100755 index 0000000000..afcad2103a --- /dev/null +++ b/test/exceptions/test.sh @@ -0,0 +1,5 @@ +#!/bin/bash +source ../test_base + +make +start test_exceptions.img diff --git a/test/fat/banana.ascii b/test/fat/banana.ascii new file mode 100644 index 0000000000..02f78d0cf6 --- /dev/null +++ b/test/fat/banana.ascii @@ -0,0 +1,15 @@ +const std::string internal_banana= +R"( ____ ___ + | _ \ ___ _ _.' _ `. + _ | [_) )' _ `._ _ ___ ! \ | | (_) | _ +|:;.| _ <| (_) | \ | |' _ `| \| | _ | .:;| +| `.[_) ) _ | \| | (_) | | | | |.',..| +':. `. /| | | | | _ | |\ | | |.' :;::' + !::, `-!_| | | |\ | | | | | \ !_!.' ':;! + !::; ":;:!.!.\_!_!_!.!-'-':;:'' '''! + ';:' `::;::;' '' ., . + `: .,. `' .::... . .::;::;' + `..:;::;:.. ::;::;:;:;, :;::;' + "-:;::;:;: ':;::;:'' ;.-' + ""`---...________...---'"" +)"; diff --git a/test/fat/fat16.cpp b/test/fat/fat16.cpp index 34c938a18b..d125b21ef5 100644 --- a/test/fat/fat16.cpp +++ b/test/fat/fat16.cpp @@ -6,9 +6,9 @@ // 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. @@ -21,81 +21,84 @@ #include +// Includes std::string internal_banana +#include "banana.ascii" + void Service::start() { INFO("FAT16", "Running tests for FAT16"); auto disk = fs::new_shared_memdisk(); assert(disk); - + // verify that the size is indeed N sectors - CHECK(disk->dev().size() == 16500, "Disk size 16500 sectors"); - assert(disk->dev().size() == 16500); - + CHECKSERT(disk->dev().size() == 16500, "Disk size 16500 sectors"); + // which means that the disk can't be empty - CHECK(!disk->empty(), "Disk not empty"); - assert(!disk->empty()); - + CHECKSERT(!disk->empty(), "Disk not empty"); + // auto-mount filesystem disk->mount( [disk] (fs::error_t err) { - CHECK(!err, "Filesystem auto-mounted"); - assert(!err); - + CHECKSERT(!err, "Filesystem auto-mounted"); + auto& fs = disk->fs(); printf("\t\t%s filesystem\n", fs.name().c_str()); - - auto vec = fs::new_shared_vector(); - err = fs.ls("/", vec); - CHECK(!err, "List root directory"); - assert(!err); - - CHECK(vec->size() == 1, "Exactly one ent in root dir"); - assert(vec->size() == 1); - - auto& e = vec->at(0); - CHECK(e.is_file(), "Ent is a file"); - CHECK(e.name() == "banana.txt", "Ent is 'banana.txt'"); - + + auto list = fs.ls("/"); + CHECKSERT(!list.error, "List root directory"); + + CHECKSERT(list.entries->size() == 1, "Exactly one ent in root dir"); + + auto& e = list.entries->at(0); + CHECKSERT(e.is_file(), "Ent is a file"); + CHECKSERT(e.name() == "banana.txt", "Ents name is 'banana.txt'"); + }); // re-mount on VBR1 disk->mount(disk->VBR1, [disk] (fs::error_t err) { - CHECK(!err, "Filesystem mounted on VBR1"); - assert(!err); - - // verify that we can read Makefile + CHECKSERT(!err, "Filesystem mounted on VBR1"); + + // verify that we can read file auto& fs = disk->fs(); auto ent = fs.stat("/banana.txt"); - CHECK(ent.is_valid(), "Stat file in root dir"); - CHECK(ent.is_file(), "Entity is file"); - CHECK(!ent.is_dir(), "Entity is not directory"); - CHECK(ent.name() == "banana.txt", "Name is 'banana.txt'"); - + CHECKSERT(ent.is_valid(), "Stat file in root dir"); + CHECKSERT(ent.is_file(), "Entity is file"); + CHECKSERT(!ent.is_dir(), "Entity is not directory"); + CHECKSERT(ent.name() == "banana.txt", "Name is 'banana.txt'"); + + printf("%s\n", internal_banana.c_str()); + // try reading banana-file - auto buf = fs.read(ent, 0, ent.size); - std::string banana((char*) buf.buffer.get(), buf.len); - - std::string internal_banana = - R"( ____ ___ - | _ \ ___ _ _.' _ `. - _ | [_) )' _ `._ _ ___ ! \ | | (_) | _ -|:;.| _ <| (_) | \ | |' _ `| \| | _ | .:;| -| `.[_) ) _ | \| | (_) | | | | |.',..| -':. `. /| | | | | _ | |\ | | |.' :;::' - !::, `-!_| | | |\ | | | | | \ !_!.' ':;! - !::; ":;:!.!.\_!_!_!.!-'-':;:'' '''! - ';:' `::;::;' '' ., . - `: .,. `' .::... . .::;::;' - `..:;::;:.. ::;::;:;:;, :;::;' - "-:;::;:;: ':;::;:'' ;.-' - ""`---...________...---'"" -)"; - CHECK(banana == internal_banana, "Correct banana"); - printf("%s\n", banana.c_str()); - + auto buf = fs.read(ent, 0, ent.size()); + auto banana = buf.to_string(); + + CHECKSERT(banana == internal_banana, "Correct banana #1"); + + bool test = true; + + for (size_t i = 0; i < internal_banana.size(); i++) + { + // read one byte at a time + buf = fs.read(ent, i, 1); + /// @buf should evaluate to 'true' if its valid + CHECKSERT(buf, "Validate buffer"); + + // verify that it matches the same location in test-string + test = ((char) buf.buffer.get()[0] == internal_banana[i]); + if (!test) { + printf("!! Random access read test failed on i = %u\n", i); + break; + } + } + CHECKSERT(test, "Validate random access sync read"); + + buf = fs.readFile("/banana.txt"); + banana = buf.to_string(); + CHECKSERT(banana == internal_banana, "Correct banana #2"); }); - + INFO("FAT16", "SUCCESS"); } diff --git a/test/fat/fat32.cpp b/test/fat/fat32.cpp index 213ab575b6..763a2f463d 100644 --- a/test/fat/fat32.cpp +++ b/test/fat/fat32.cpp @@ -6,9 +6,9 @@ // 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. @@ -21,46 +21,44 @@ #include #include -using FatDisk = fs::Disk; -std::shared_ptr disk; + +// Includes std::string internal_banana +#include "banana.ascii" + +std::shared_ptr disk; void Service::start() { INFO("FAT32", "Running tests for FAT32"); auto& device = hw::Dev::disk<0, hw::IDE>(hw::IDE::SLAVE); - disk = std::make_shared (device); + disk = std::make_shared (device); assert(disk); - + // verify that the size is indeed N sectors const size_t SIZE = 4194304; - CHECK(disk->dev().size() == SIZE, "Disk size 4194304 sectors"); - assert(disk->dev().size() == SIZE); - + printf("Size: %llu\n", disk->dev().size()); + CHECKSERT(disk->dev().size() == SIZE, "Disk size 4194304 sectors"); + // which means that the disk can't be empty - CHECK(!disk->empty(), "Disk not empty"); - assert(!disk->empty()); - + CHECKSERT(!disk->empty(), "Disk not empty"); + // auto-mount filesystem disk->mount(disk->MBR, [] (fs::error_t err) { - CHECK(!err, "Filesystem auto-mounted"); - assert(!err); - + CHECKSERT(!err, "Filesystem auto-mounted"); + auto& fs = disk->fs(); printf("\t\t%s filesystem\n", fs.name().c_str()); - - auto vec = fs::new_shared_vector(); - err = fs.ls("/", vec); - CHECK(!err, "List root directory"); - assert(!err); - - CHECK(vec->size() == 2, "Exactly two ents in root dir"); - assert(vec->size() == 2); - - auto& e = vec->at(0); - CHECK(e.is_file(), "Ent is a file"); - CHECK(e.name() == "banana.txt", "Ent is 'banana.txt'"); + + auto list = fs.ls("/"); + CHECKSERT(!list.error, "List root directory"); + + CHECKSERT(list.entries->size() == 2, "Exactly two ents in root dir"); + + auto& e = list.entries->at(0); + CHECKSERT(e.is_file(), "Ent is a file"); + CHECKSERT(e.name() == "banana.txt", "Ents name is 'banana.txt'"); }); // re-mount on VBR1 disk->mount(disk->VBR1, @@ -68,35 +66,48 @@ void Service::start() { CHECK(!err, "Filesystem mounted on VBR1"); assert(!err); - + auto& fs = disk->fs(); auto ent = fs.stat("/banana.txt"); - CHECK(ent.is_valid(), "Stat file in root dir"); - assert(ent.is_valid()); - - CHECK(ent.is_file(), "Entity is file"); - assert(ent.is_file()); - - CHECK(!ent.is_dir(), "Entity is not directory"); - assert(!ent.is_dir()); - - CHECK(ent.name() == "banana.txt", "Name is 'banana.txt'"); - assert(ent.name() == "banana.txt"); - + CHECKSERT(ent.is_valid(), "Stat file in root dir"); + CHECKSERT(ent.is_file(), "Entity is file"); + CHECKSERT(!ent.is_dir(), "Entity is not directory"); + CHECKSERT(ent.name() == "banana.txt", "Name is 'banana.txt'"); + ent = fs.stat("/dir1/dir2/dir3/dir4/dir5/dir6/banana.txt"); - CHECK(ent.is_valid(), "Stat file in deep dir"); - assert(ent.is_valid()); - - CHECK(ent.is_file(), "Entity is file"); - assert(ent.is_file()); - - CHECK(!ent.is_dir(), "Entity is not directory"); - assert(!ent.is_dir()); - - CHECK(ent.name() == "banana.txt", "Name is 'banana.txt'"); - assert(ent.name() == "banana.txt"); - + CHECKSERT(ent.is_valid(), "Stat file in deep dir"); + CHECKSERT(ent.is_file(), "Entity is file"); + CHECKSERT(!ent.is_dir(), "Entity is not directory"); + + CHECKSERT(ent.name() == "banana.txt", "Name is 'banana.txt'"); + + printf("%s\n", internal_banana.c_str()); + + // asynch file reading test + fs.read(ent, 0, ent.size(), + [&fs] (fs::error_t err, fs::buffer_t buf, uint64_t len) + { + CHECKSERT(!err, "read: Read 'banana.txt' asynchronously"); + if (err) { + panic("Failed to read file (async)"); + } + + std::string banana((char*) buf.get(), len); + CHECKSERT(banana == internal_banana, "Correct banana #1"); + + fs.readFile("/banana.txt", + [&fs] (fs::error_t err, fs::buffer_t buf, uint64_t len) + { + CHECKSERT(!err, "readFile: Read 'banana.txt' asynchronously"); + if (err) { + panic("Failed to read file (async)"); + } + + std::string banana((char*) buf.get(), len); + CHECKSERT(banana == internal_banana, "Correct banana #2"); + }); + }); }); - + INFO("FAT32", "SUCCESS"); } diff --git a/test/fat/test.sh b/test/fat/test.sh index b684926ed3..e347a79140 100755 --- a/test/fat/test.sh +++ b/test/fat/test.sh @@ -1,6 +1,6 @@ #!/bin/bash source ../test_base -mkdir tmpdisk +mkdir -p tmpdisk ### FAT16 TEST ### rm -f my.disk @@ -19,7 +19,7 @@ rm -f memdisk.o my.disk ### FAT16 TEST ### fallocate -l 2147483648 my.disk mkfs.fat my.disk -mkdir tmpdisk +mkdir -p tmpdisk sudo mount my.disk tmpdisk/ sudo cp banana.txt tmpdisk/ sudo mkdir -p tmpdisk/dir1/dir2/dir3/dir4/dir5/dir6 diff --git a/test/memdisk/bigdisk.cpp b/test/memdisk/bigdisk.cpp index d04b021d5e..3d76c9f528 100644 --- a/test/memdisk/bigdisk.cpp +++ b/test/memdisk/bigdisk.cpp @@ -38,9 +38,9 @@ void Service::start() // verify nothing bad happened CHECK(!!(buf), "Buffer for sector 0 is valid"); if (!buf) - { - panic("Failed to read sector 0 on memdisk device\n"); - } + { + panic("Failed to read sector 0 on memdisk device\n"); + } // verify MBR has signature const uint8_t* mbr = buf.get(); assert(mbr[0x1FE] == 0x55); diff --git a/test/memdisk/test.sh b/test/memdisk/test.sh index 167000decf..e0915da628 100755 --- a/test/memdisk/test.sh +++ b/test/memdisk/test.sh @@ -19,8 +19,9 @@ sudo cp banana.txt tmpdisk/ sync # Mui Importante make SERVICE=Test DISK=big.disk FILES=bigdisk.cpp +export MEM="-m 256" start Test.img "Memdisk: Big disk test" make SERVICE=Test FILES=bigdisk.cpp clean sudo umount tmpdisk/ -rm -f big.disk +rm -f big.disk memdisk.o diff --git a/test/memdisk/twosector.cpp b/test/memdisk/twosector.cpp index a0e3357bb4..99e29aaf1d 100644 --- a/test/memdisk/twosector.cpp +++ b/test/memdisk/twosector.cpp @@ -38,9 +38,9 @@ void Service::start() // verify nothing bad happened CHECK(!!(buf), "Buffer for sector 0 is valid"); if (!buf) - { - panic("Failed to read sector 0 on memdisk device\n"); - } + { + panic("Failed to read sector 0 on memdisk device\n"); + } // convert to text std::string text((const char*) buf.get(), disk->dev().block_size()); // verify that the sector contents matches the test string @@ -53,7 +53,7 @@ void Service::start() // verify that reading outside of disk returns a 0x0 pointer buf = disk->dev().read_sync(disk->dev().size()); CHECK(!buf, "Buffer outside of disk range (sector=%llu) is 0x0", - disk->dev().size()); + disk->dev().size()); assert(!buf); INFO("MemDisk", "SUCCESS"); diff --git a/test/quick_exit/Makefile b/test/quick_exit/Makefile index 749e17842f..c4018195c9 100644 --- a/test/quick_exit/Makefile +++ b/test/quick_exit/Makefile @@ -3,132 +3,18 @@ ################################################# # The name of your service -SERVICE = Test_quick_exit +SERVICE = test_quick_exit +SERVICE_NAME = Quick Exit Test # Your service parts FILES = service.cpp +# Your disk image +DISK= +LOCAL_INCLUDES= # IncludeOS location ifndef INCLUDEOS_INSTALL - INCLUDEOS_INSTALL=$(HOME)/IncludeOS_install +INCLUDEOS_INSTALL=$(HOME)/IncludeOS_install endif -# Shorter name -INSTALL = $(INCLUDEOS_INSTALL) - -# Compiler/Linker -################################################### - -OPTIONS = -Ofast -msse3 -Wall -Wextra -mstackrealign - -# External Libraries -################################################### -LIBC_OBJ = $(INSTALL)/newlib/libc.a -LIBG_OBJ = $(INSTALL)/newlib/libg.a -LIBM_OBJ = $(INSTALL)/newlib/libm.a - -LIBGCC = $(INSTALL)/libgcc/libgcc.a -LIBCXX = $(INSTALL)/libcxx/libc++.a $(INSTALL)/libcxx/libc++abi.a - - -INC_NEWLIB=$(INSTALL)/newlib/include -INC_LIBCXX=$(INSTALL)/libcxx/include - -DEBUG_OPTS = -ggdb3 -v - -CPP = clang++-3.6 -target i686-elf -LD = ld - -INCLUDES = -I$(INC_LIBCXX) -I$(INSTALL)/api/sys -I$(INC_NEWLIB) -I$(INSTALL)/api - -CAPABS_COMMON = -msse3 -mstackrealign # Needed for 16-byte stack alignment (SSE) - -all: CAPABS = $(CAPABS_COMMON) -O2 -debug: CAPABS = $(CAPABS_COMMON) -O0 -stripped: CAPABS = $(CAPABS_COMMON) -Oz - -WARNS = -Wall -Wextra #-pedantic -CPPOPTS = $(CAPABS) $(WARNS) -c -m32 -std=c++14 -fno-stack-protector $(INCLUDES) -D_LIBCPP_HAS_NO_THREADS=1 #-flto -fno-exceptions - -LDOPTS = -nostdlib -melf_i386 -N --script=$(INSTALL)/linker.ld -flto - - -# Objects -################################################### - -CRTBEGIN_OBJ = $(INSTALL)/crt/crtbegin.o -CRTEND_OBJ = $(INSTALL)/crt/crtend.o -CRTI_OBJ = $(INSTALL)/crt/crti.o -CRTN_OBJ = $(INSTALL)/crt/crtn.o - -# Full link list -OBJS = $(FILES:.cpp=.o) .service_name.o -LIBS = $(INSTALL)/os.a $(LIBCXX) $(INSTALL)/os.a $(LIBC_OBJ) $(LIBM_OBJ) $(LIBGCC) - -OS_PRE = $(CRTBEGIN_OBJ) $(CRTI_OBJ) -OS_POST = $(CRTEND_OBJ) $(CRTN_OBJ) - -DEPS = $(OBJS:.o=.d) - -# Complete bulid -################################################### -# A complete build includes: -# - a "service", to be linked with OS-objects (OS included) - -all: service - -stripped: LDOPTS += -S #strip all -stripped: CPPOPTS += -Oz -stripped: service - - -# The same, but with debugging symbols (OBS: Dramatically increases binary size) -debug: CCOPTS += $(DEBUG_OPTS) -debug: CPPOPTS += $(DEBUG_OPTS) -debug: LDOPTS += -M --verbose - -debug: OBJS += $(LIBG_OBJ) - -debug: service #Don't wanna call 'all', since it strips debug info - -# Service -################################################### -service.o: service.cpp - @echo "\n>> Compiling the service" - $(CPP) $(CPPOPTS) -o $@ $< - -.service_name.o: $(INSTALL)/service_name.cpp - $(CPP) $(CPPOPTS) -DSERVICE_NAME="\"$(SERVICE)\"" -o $@ $< - -# Link the service with the os -service: $(OBJS) $(LIBS) - @echo "\n>> Linking service with OS" - $(LD) $(LDOPTS) $(OS_PRE) $(OBJS) $(LIBS) $(OS_POST) -o $(SERVICE) - @echo "\n>> Building image " $(SERVICE).img - $(INSTALL)/vmbuild $(INSTALL)/bootloader $(SERVICE) - -# Object files -################################################### - -# Runtime -crt%.o: $(INSTALL)/crt/crt%.s - @echo "\n>> Assembling C runtime:" $@ - $(CPP) $(CPPOPTS) -x assembler-with-cpp $< - -# General C++-files to object files -%.o: %.cpp - @echo "\n>> Compiling OS object without header" - $(CPP) $(CPPOPTS) -o $@ $< - -# AS-assembled object files -%.o: %.s - @echo "\n>> Assembling GNU 'as' files" - $(CPP) $(CPPOPTS) -x assembler-with-cpp $< - -# Cleanup -################################################### -clean: - $(RM) $(OBJS) $(DEPS) $(SERVICE) - $(RM) $(SERVICE).img - --include $(DEPS) +include $(INCLUDEOS_INSTALL)/Makeseed diff --git a/test/quick_exit/debug/service.gdb b/test/quick_exit/debug/service.gdb deleted file mode 100644 index f5acb4ff3c..0000000000 --- a/test/quick_exit/debug/service.gdb +++ /dev/null @@ -1,5 +0,0 @@ -file service -break _start -break OS::start -set non-stop off -target remote localhost:1234 \ No newline at end of file diff --git a/test/quick_exit/service.cpp b/test/quick_exit/service.cpp index 4504e901b0..cceea17662 100644 --- a/test/quick_exit/service.cpp +++ b/test/quick_exit/service.cpp @@ -17,15 +17,29 @@ #include #include +#include +/** + Test 'quick-exit' and 'at_quick_exit' from C11 + + NOTE: This test is supposed to result in a PANIC, + after calling the quick-exit handler, and then quick_exit + +**/ void Service::start() { - printf("TESTING Quick Exit\n"); + INFO("Test Quick Exit","Expect panic after exit handler"); + + at_quick_exit([](){ + CHECK(1,"Custom quick exit-handler called"); + return; + }); - at_quick_exit([](){ printf("[x] Custom quick exit-handler called \n"); return; }); quick_exit(0); // Make sure we actually exit (This is dead code - but for testing) - at_quick_exit([](){ printf("[0] Service didn't actually exit \n"); return; }); + at_quick_exit([](){ + CHECK(0, "FAIL: Service didn't actually exit \n"); return; }); + } diff --git a/test/serial/Makefile b/test/serial/Makefile new file mode 100644 index 0000000000..9b39995ca6 --- /dev/null +++ b/test/serial/Makefile @@ -0,0 +1,21 @@ +################################################# +# IncludeOS SERVICE makefile # +################################################# + +# The name of your service +SERVICE = test_serial +SERVICE_NAME = Serial port test + +# Your service parts +FILES = service.cpp + +# Your disk image +DISK= +LOCAL_INCLUDES= + +# IncludeOS location +ifndef INCLUDEOS_INSTALL +INCLUDEOS_INSTALL=$(HOME)/IncludeOS_install +endif + +include $(INCLUDEOS_INSTALL)/Makeseed diff --git a/test/serial/run.sh b/test/serial/run.sh new file mode 100755 index 0000000000..3b2132d2cb --- /dev/null +++ b/test/serial/run.sh @@ -0,0 +1,3 @@ +#! /bin/bash +source ${INCLUDEOS_HOME-$HOME/IncludeOS_install}/etc/run.sh + diff --git a/test/serial/service.cpp b/test/serial/service.cpp new file mode 100644 index 0000000000..fe959d1bdf --- /dev/null +++ b/test/serial/service.cpp @@ -0,0 +1,37 @@ +// This file is a part of the IncludeOS unikernel - www.includeos.org +// +// Copyright 2015 Oslo and Akershus University College of Applied Sciences +// and Alfred Bratterud +// +// 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. + +#include +#include +#include + +using namespace std::chrono; + +void Service::start() +{ + + auto& com1 = hw::Serial::port<1>(); + + com1.on_readline([](const std::string& s){ + printf("\nReceived: %s \n", s.c_str()); + + }); + + INFO("Serial Test","Doing some serious serial"); + + +} diff --git a/test/tcp/Makefile b/test/tcp/Makefile index 7d7164a3f7..486480e48a 100644 --- a/test/tcp/Makefile +++ b/test/tcp/Makefile @@ -3,8 +3,8 @@ ################################################# # The name of your service -SERVICE = "IncludeOS_TCP_Test" -SERVICE_NAME = "TCP\ Test\ Service" +SERVICE = test_tcp +SERVICE_NAME = TCP Test Service # Your service parts FILES = service.cpp diff --git a/test/tcp/README.md b/test/tcp/README.md index 1658ef3c24..c744227e03 100644 --- a/test/tcp/README.md +++ b/test/tcp/README.md @@ -1,7 +1,8 @@ # TCP Test ### Usage: -` $ python test.py $GUEST_IP $HOST_IP ` +1. Start the TCP test service, with `./test.sh` +2. Start the python test with `$ python test.py $GUEST_IP $HOST_IP ` Guest and Host IP are optional (default are GUEST=`10.0.0.42` HOST=`10.0.0.1`). @@ -18,7 +19,7 @@ This test is developed on OS X using Virtualbox. Here's an recepie how to try it 4. Open Virtualbox => TCP_test => Settings => Network 5. Change *Adapter 1* to use *NAT* => Advanced => Port Forwarding 6. Add the following rules: - + ``` $ vboxmanage showvminfo TCP_test ... diff --git a/test/tcp/service.cpp b/test/tcp/service.cpp index 3cfc4fd499..50755612c8 100644 --- a/test/tcp/service.cpp +++ b/test/tcp/service.cpp @@ -6,9 +6,9 @@ // 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. @@ -25,226 +25,275 @@ using namespace net; using namespace std::chrono; // For timers and MSL using Connection_ptr = std::shared_ptr; +using buffer_t = TCP::buffer_t; std::unique_ptr> inet; std::shared_ptr client; /* - TEST VARIABLES + TEST VARIABLES */ -TCP::Port - TEST1{8081}, TEST2{8082}, TEST3{8083}, TEST4{8084}, TEST5{8085}; +TCP::Port +TEST1{8081}, TEST2{8082}, TEST3{8083}, TEST4{8084}, TEST5{8085}; using HostAddress = std::pair; HostAddress - TEST_ADDR_TIME{"india.colorado.edu", 13}; +TEST_ADDR_TIME{"india.colorado.edu", 13}; -std::string - small, big, huge; +std::string +small, big, huge; -int - S{150}, B{1500}, H{15000}; +int +S{150}, B{1500}, H{150000}; -std::string - TEST_STR {"1337"}; +std::string +TEST_STR {"Kappa!"}; -size_t bufstore_capacity{0}; +size_t buffers_available{0}; // Default MSL is 30s. Timeout 2*MSL. // To reduce test duration, lower MSL to 5s. milliseconds MSL_TEST = 5s; /* - TEST: Release of resources/clean up. + TEST: Release of resources/clean up. */ void FINISH_TEST() { - INFO("TEST", "Started 3 x MSL timeout."); - hw::PIT::instance().onTimeout(3 * MSL_TEST, [] { - INFO("TEST", "Verify release of resources"); - CHECK(inet->tcp().activeConnections() == 0, "tcp.activeConnections() == 0"); - CHECK(inet->available_capacity() == bufstore_capacity, - "inet.available_capacity() == bufstore_capacity"); - printf("# TEST DONE #\n"); - }); + INFO("TEST", "Started 3 x MSL timeout."); + hw::PIT::instance().onTimeout(3 * MSL_TEST, [] { + INFO("TEST", "Verify release of resources"); + CHECK(inet->tcp().activeConnections() == 0, + "tcp.activeConnections() == 0"); + CHECK(inet->buffers_available() == buffers_available, + "inet->buffers_available() == buffers_available"); + printf("# TEST SUCCESS #\n"); + }); } /* - TEST: Outgoing Internet Connection + TEST: Outgoing Internet Connection */ void OUTGOING_TEST_INTERNET(const HostAddress& address) { - auto port = address.second; - INFO("TEST", "Outgoing Internet Connection (%s:%u)", address.first.c_str(), address.second); - inet->resolve(address.first, [port](auto&, auto&, auto ip_address) { - CHECK(ip_address != 0, "Resolved host"); - - if(ip_address != 0) { - inet->tcp().connect(ip_address, port) - ->onConnect([](Connection_ptr) { - CHECK(true, "Connected"); - }) - .onReceive([](Connection_ptr conn, bool) { - CHECK(true, "Received data: %s", conn->read().c_str()); - }) - .onError([](Connection_ptr, TCP::TCPException err) { - CHECK(false, "Error occured: %s", err.what()); - }); - } - }); + auto port = address.second; + INFO("TEST", "Outgoing Internet Connection (%s:%u)", address.first.c_str(), address.second); + inet->resolve(address.first, + [port](auto ip_address) { + CHECK(ip_address != 0, "Resolved host"); + + if(ip_address != 0) { + inet->tcp().connect(ip_address, port) + ->onConnect([](Connection_ptr conn) { + CHECK(true, "Connected"); + conn->read(1024, [](buffer_t, size_t n) { + CHECK(n > 0, "Received data"); + }); + }) + .onError([](Connection_ptr, TCP::TCPException err) { + CHECK(false, "Error occured: %s", err.what()); + }); + } + }); } /* - TEST: Outgoing Connection to Host + TEST: Outgoing Connection to Host */ void OUTGOING_TEST(TCP::Socket outgoing) { - INFO("TEST", "Outgoing Connection (%s)", outgoing.to_string().c_str()); - inet->tcp().connect(outgoing) - ->onConnect([](Connection_ptr conn) { - conn->write(small); - }) - .onReceive([](Connection_ptr conn, bool) { - CHECK(conn->read() == small, "conn->read() == small"); - }) - .onDisconnect([](Connection_ptr, std::string) { - CHECK(true, "Connection closed by server"); - - OUTGOING_TEST_INTERNET(TEST_ADDR_TIME); - }); + INFO("TEST", "Outgoing Connection (%s)", outgoing.to_string().c_str()); + inet->tcp().connect(outgoing) + ->onConnect([](Connection_ptr conn) { + conn->write(small.data(), small.size()); + conn->read(small.size(), [](buffer_t buffer, size_t n) { + CHECK(std::string((char*)buffer.get(), n) == small, "conn->read() == small"); + }); + }) + .onDisconnect([](Connection_ptr, TCP::Connection::Disconnect) { + CHECK(true, "Connection closed by server"); + + OUTGOING_TEST_INTERNET(TEST_ADDR_TIME); + }); } +/*void outgoing_packet(net::Packet_ptr p) { + + auto* eth = reinterpret_cast(p->buffer()); + + if (eth->type == net::Ethernet::ETH_IP4) { + + auto ip4 = net::view_packet_as(p); + auto& hdr = reinterpret_cast(p->buffer())->ip_hdr; + + if (hdr.protocol == net::IP4::IP4_TCP) { + auto tcp = net::view_packet_as(p); + printf("%s\n", tcp->to_string().c_str()); + } + } + +}*/ + // Used to send big data struct Buffer { - size_t written, read; - char* data; - const size_t size; - - Buffer(size_t length) : - written(0), read(0), data(new char[length]), size(length) {} + size_t written, read; + char* data; + const size_t size; + + Buffer(size_t length) : + written(0), read(0), data(new char[length]), size(length) {} - ~Buffer() { delete[] data; } + ~Buffer() { delete[] data; } - std::string str() { return {data, size};} + std::string str() { return {data, size};} }; +void print_stuff() +{ + printf("TIMER: Buffers avail: %u / %u Transmit avail: %u\n", + inet->buffers_available(), buffers_available, inet->transmit_queue_available()); + hw::PIT::on_timeout(5.0, print_stuff); +} + void Service::start() { - for(int i = 0; i < S; i++) small += TEST_STR; - for(int i = 0; i < B; i++) big += TEST_STR; - for(int i = 0; i < H; i++) huge += TEST_STR; + //hw::PIT::on_timeout(5.0, print_stuff); + + IP4::addr A1 (255, 255, 255, 255); + IP4::addr B1 ( 0, 255, 255, 255); + IP4::addr C1 ( 0, 0, 255, 255); + IP4::addr D1 ( 0, 0, 0, 255); + IP4::addr E1 ( 0, 0, 0, 0); + printf("A: %s\n", A1.str().c_str()); + printf("B: %s\n", B1.str().c_str()); + printf("C: %s\n", C1.str().c_str()); + printf("D: %s\n", D1.str().c_str()); + printf("E: %s\n", E1.str().c_str()); + printf("D & A: %s\n", (D1 & A1).str().c_str()); + + for(int i = 0; i < S; i++) small += TEST_STR; + + big += "start-"; + for(int i = 0; i < B; i++) big += TEST_STR; + big += "-end"; - hw::Nic& eth0 = hw::Dev::eth<0,VirtioNet>(); + huge = "start-"; + for(int i = 0; i < H; i++) huge += TEST_STR; + huge += "-end"; + + hw::Nic& eth0 = hw::Dev::eth<0,VirtioNet>(); + //eth0.on_exit_to_physical(outgoing_packet); inet = std::make_unique>(eth0); + inet->network_config( { 10, 0, 0, 42 }, // IP + { 255,255,255, 0 }, // Netmask + { 10, 0, 0, 1 }, // Gateway + { 8, 8, 8, 8 } );// DNS - inet->network_config( {{ 10,0,0,42 }}, // IP - {{ 255,255,255,0 }}, // Netmask - {{ 10,0,0,1 }}, // Gateway - {{ 8,8,8,8 }} ); // DNS - - bufstore_capacity = inet->available_capacity(); - auto& tcp = inet->tcp(); - // this is default - tcp.set_buffer_limit(10); - // reduce test duration - tcp.set_MSL(MSL_TEST); - - /* - TEST: Send and receive small string. - */ - INFO("TEST", "Listeners and connections allocation."); - - /* - TEST: Nothing should be allocated. - */ - CHECK(tcp.openPorts() == 0, "tcp.openPorts() == 0"); - CHECK(tcp.activeConnections() == 0, "tcp.activeConnections() == 0"); - - tcp.bind(TEST1).onConnect([](Connection_ptr conn) { - INFO("TEST", "SMALL string"); - conn->onReceive([](Connection_ptr conn, bool) { - CHECK(conn->read() == small, "conn.read() == small"); - conn->close(); - }); - conn->write(small); - }); - - /* - TEST: Server should be bound. - */ - CHECK(tcp.openPorts() == 1, "tcp.openPorts() == 1"); - - /* - TEST: Send and receive big string. - */ - tcp.bind(TEST2).onConnect([](Connection_ptr conn) { - INFO("TEST", "BIG string"); - auto response = std::make_shared(); - conn->onReceive([response](Connection_ptr conn, bool) { - *response += conn->read(); - if(response->size() == big.size()) { - bool OK = (*response == big); - CHECK(OK, "conn.read() == big"); - conn->close(); - } - }); - conn->write(big); - }); - - /* - TEST: Send and receive huge string. - */ - tcp.bind(TEST3).onConnect([](Connection_ptr conn) { - INFO("TEST", "HUGE string"); - auto buffer = std::make_shared(huge.size()); - conn->onReceive([buffer](Connection_ptr conn, bool) { - // if not all expected data is read - if(buffer->read < huge.size()) - buffer->read += conn->read(buffer->data+buffer->read, conn->receive_buffer().data_size()); - // if not all expected data is written - if(buffer->written < huge.size()) { - buffer->written += conn->write(huge.data()+buffer->written, huge.size() - buffer->written); - } - // when all expected data is read - if(buffer->read == huge.size()) { - bool OK = (buffer->str() == huge); - CHECK(OK, "conn.read() == huge"); - conn->close(); - } - }); - buffer->written += conn->write(huge.data(), huge.size()); - }); - - /* - TEST: More servers should be bound. - */ - CHECK(tcp.openPorts() == 3, "tcp.openPorts() == 3"); - - /* - TEST: Connection (Status etc.) and Active Close - */ - tcp.bind(TEST4).onConnect([](Connection_ptr conn) { - INFO("TEST","Connection"); - // There should be at least one connection. - CHECK(inet->tcp().activeConnections() > 0, "tcp.activeConnections() > 0"); - // Test if connected. - CHECK(conn->is_connected(), "conn.is_connected()"); - // Test if writable. - CHECK(conn->is_writable(), "conn.is_writable()"); - // Test if state is ESTABLISHED. - CHECK(conn->is_state({"ESTABLISHED"}), "conn.is_state(ESTABLISHED)"); - - INFO("TEST", "Active close"); - // Test for active close. - conn->close(); - CHECK(!conn->is_writable(), "!conn->is_writable()"); - CHECK(conn->is_state({"FIN-WAIT-1"}), "conn.is_state(FIN-WAIT-1)"); - }) - .onDisconnect([](Connection_ptr conn, std::string) { - CHECK(conn->is_state({"FIN-WAIT-2"}), "conn.is_state(FIN-WAIT-2)"); - hw::PIT::instance().onTimeout(1s,[conn]{ - CHECK(conn->is_state({"TIME-WAIT"}), "conn.is_state(TIME-WAIT)"); - - OUTGOING_TEST({inet->router(), TEST5}); - }); - - hw::PIT::instance().onTimeout(5s, [] { FINISH_TEST(); }); - }); + buffers_available = inet->buffers_available(); + INFO("Buffers available", "%u", inet->buffers_available()); + auto& tcp = inet->tcp(); + // reduce test duration + tcp.set_MSL(MSL_TEST); + + /* + TEST: Send and receive small string. + */ + INFO("TEST", "Listeners and connections allocation."); + + /* + TEST: Nothing should be allocated. + */ + CHECK(tcp.openPorts() == 0, "tcp.openPorts() == 0"); + CHECK(tcp.activeConnections() == 0, "tcp.activeConnections() == 0"); + + tcp.bind(TEST1).onConnect([](Connection_ptr conn) { + INFO("TEST", "SMALL string (%u)", small.size()); + conn->read(small.size(), [conn](buffer_t buffer, size_t n) { + CHECK(inet->buffers_available() < buffers_available, + "inet->buffers_available() < buffers_available"); + CHECK(std::string((char*)buffer.get(), n) == small, "conn.read() == small"); + conn->close(); + }); + conn->write(small.data(), small.size()); + INFO("Buffers available", "%u", inet->buffers_available()); + }); + + /* + TEST: Server should be bound. + */ + CHECK(tcp.openPorts() == 1, "tcp.openPorts() == 1"); + + /* + TEST: Send and receive big string. + */ + tcp.bind(TEST2).onConnect([](Connection_ptr conn) { + INFO("TEST", "BIG string (%u)", big.size()); + auto response = std::make_shared(); + conn->read(big.size(), [response, conn](buffer_t buffer, size_t n) { + *response += std::string((char*)buffer.get(), n); + if(response->size() == big.size()) { + bool OK = (*response == big); + CHECK(OK, "conn.read() == big"); + conn->close(); + } + }); + conn->write(big.data(), big.size()); + INFO("Buffers available", "%u", inet->buffers_available()); + }); + + /* + TEST: Send and receive huge string. + */ + tcp.bind(TEST3).onConnect([](Connection_ptr conn) { + INFO("TEST", "HUGE string (%u)", huge.size()); + auto temp = std::make_shared(huge.size()); + conn->read(huge.size(), [temp, conn](buffer_t buffer, size_t n) { + memcpy(temp->data + temp->written, buffer.get(), n); + temp->written += n; + //printf("Read: %u\n", n); + // when all expected data is read + if(temp->written == huge.size()) { + bool OK = (temp->str() == huge); + CHECK(OK, "conn.read() == huge"); + conn->close(); + } + }); + conn->write(huge.data(), huge.size(), [](size_t n) { + printf("Finished write request! %u bytes written\n", n); + }, true); + INFO("Buffers available", "%u", inet->buffers_available()); + }); + + /* + TEST: More servers should be bound. + */ + CHECK(tcp.openPorts() == 3, "tcp.openPorts() == 3"); + + /* + TEST: Connection (Status etc.) and Active Close + */ + tcp.bind(TEST4).onConnect([](Connection_ptr conn) { + INFO("TEST","Connection"); + // There should be at least one connection. + CHECK(inet->tcp().activeConnections() > 0, "tcp.activeConnections() > 0"); + // Test if connected. + CHECK(conn->is_connected(), "conn.is_connected()"); + // Test if writable. + CHECK(conn->is_writable(), "conn.is_writable()"); + // Test if state is ESTABLISHED. + CHECK(conn->is_state({"ESTABLISHED"}), "conn.is_state(ESTABLISHED)"); + + INFO("TEST", "Active close"); + // Test for active close. + conn->close(); + CHECK(!conn->is_writable(), "!conn->is_writable()"); + CHECK(conn->is_state({"FIN-WAIT-1"}), "conn.is_state(FIN-WAIT-1)"); + }) + .onDisconnect([](Connection_ptr conn, TCP::Connection::Disconnect) { + CHECK(conn->is_state({"FIN-WAIT-2"}), "conn.is_state(FIN-WAIT-2)"); + hw::PIT::instance().onTimeout(1s,[conn]{ + CHECK(conn->is_state({"TIME-WAIT"}), "conn.is_state(TIME-WAIT)"); + + OUTGOING_TEST({inet->router(), TEST5}); + }); + hw::PIT::instance().onTimeout(5s, [] { FINISH_TEST(); }); + }); +printf ("IncludeOS TCP test\n"); } diff --git a/test/tcp/test.py b/test/tcp/test.py old mode 100644 new mode 100755 index 0c036b7771..ea185b60ee --- a/test/tcp/test.py +++ b/test/tcp/test.py @@ -2,60 +2,74 @@ import socket import sys +sys.path.insert(0,"..") + +import vmrunner # Usage: python test.py $GUEST_IP $HOST_IP GUEST = '10.0.0.42' if (len(sys.argv) < 2) else sys.argv[1] HOST = '10.0.0.1' if (len(sys.argv) < 3) else sys.argv[2] -def connect(port): - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - server_address = (GUEST, port) - print >>sys.stderr, 'connecting to %s port %s' % server_address - sock.connect(server_address) +def TCP_test(): + def connect(port): + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server_address = (GUEST, port) + print >>sys.stderr, 'connecting to %s port %s' % server_address + sock.connect(server_address) - try: - while True: - data = sock.recv(1024) - if data: - sock.sendall(data); - else: - break - finally: - print >>sys.stderr, 'closing socket' - sock.close() - return - -connect(8081) -connect(8082) -connect(8083) -connect(8084) - -def listen(port): - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - server_address = (HOST, port) - print >>sys.stderr, 'starting up on %s port %s' % server_address - sock.bind(server_address) - sock.listen(1) - - while True: - connection, client_address = sock.accept() try: - print >>sys.stderr, 'connection from', client_address while True: - data = connection.recv(1024) + data = sock.recv(1024) + #print >>sys.stderr, '%s' % data if data: - print >>sys.stderr, 'received data, sending data back to the client' - connection.sendall(data) - print >>sys.stderr, 'close connection to client' - connection.close() + sock.sendall(data); else: - print >>sys.stderr, 'no more data from', client_address break - finally: - connection.close() - break - sock.close() - return + print >>sys.stderr, 'closing socket' + sock.close() + return + + connect(8081) + connect(8082) + connect(8083) + connect(8084) + + def listen(port): + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server_address = (HOST, port) + print >>sys.stderr, 'starting up on %s port %s' % server_address + sock.bind(server_address) + sock.listen(1) + + while True: + connection, client_address = sock.accept() + try: + print >>sys.stderr, 'connection from', client_address + while True: + data = connection.recv(1024) + if data: + print >>sys.stderr, 'received data, sending data back to the client' + connection.sendall(data) + print >>sys.stderr, 'close connection to client' + connection.close() + else: + print >>sys.stderr, 'no more data from', client_address + break + + finally: + connection.close() + break + sock.close() + return + + listen(8085) + +# Get an auto-created VM from the vmrunner +vm = vmrunner.vms[0] + +# Add custom event-handler +vm.on_output("IncludeOS TCP test", TCP_test) -listen(8085) \ No newline at end of file +# Boot the VM, taking a timeout as parameter +vm.boot(40) \ No newline at end of file diff --git a/test/tcp/test.sh b/test/tcp/test.sh old mode 100644 new mode 100755 index e69de29bb2..ee020d5f84 --- a/test/tcp/test.sh +++ b/test/tcp/test.sh @@ -0,0 +1,6 @@ +#! /bin/bash + +source ../test_base + +make +start test_tcp.img diff --git a/test/tcp/vbox.sh b/test/tcp/vbox.sh new file mode 100755 index 0000000000..9d9444da8c --- /dev/null +++ b/test/tcp/vbox.sh @@ -0,0 +1,2 @@ +#!/bin/bash +../../etc/vboxrun.sh test_tcp.img TCP_test diff --git a/test/tcp/vm.json b/test/tcp/vm.json new file mode 100644 index 0000000000..69fc7d46a9 --- /dev/null +++ b/test/tcp/vm.json @@ -0,0 +1,6 @@ +{ + "image" : "test_tcp.img", + "net" : [{"type" : "virtio", "mac" : "c0:01:0a:00:00:2a"}], + "cpu" : "host", + "mem" : 256 +} \ No newline at end of file diff --git a/test/term/Makefile b/test/term/Makefile new file mode 100644 index 0000000000..ad0d5515b1 --- /dev/null +++ b/test/term/Makefile @@ -0,0 +1,19 @@ +################################################# +# IncludeOS SERVICE makefile # +################################################# + +# The name of your service +SERVICE = Test +SERVICE_NAME = The Terminal Test Service + +# Your service parts +FILES=term.cpp +# Your disk image +DISK= + +# IncludeOS location +ifndef INCLUDEOS_INSTALL +INCLUDEOS_INSTALL=$(HOME)/IncludeOS_install +endif + +include $(INCLUDEOS_INSTALL)/Makeseed diff --git a/test/term/banana.txt b/test/term/banana.txt new file mode 100644 index 0000000000..8e3c13d0c8 --- /dev/null +++ b/test/term/banana.txt @@ -0,0 +1,13 @@ + ____ ___ + | _ \ ___ _ _.' _ `. + _ | [_) )' _ `._ _ ___ ! \ | | (_) | _ +|:;.| _ <| (_) | \ | |' _ `| \| | _ | .:;| +| `.[_) ) _ | \| | (_) | | | | |.',..| +':. `. /| | | | | _ | |\ | | |.' :;::' + !::, `-!_| | | |\ | | | | | \ !_!.' ':;! + !::; ":;:!.!.\_!_!_!.!-'-':;:'' '''! + ';:' `::;::;' '' ., . + `: .,. `' .::... . .::;::;' + `..:;::;:.. ::;::;:;:;, :;::;' + "-:;::;:;: ':;::;:'' ;.-' + ""`---...________...---'"" diff --git a/test/term/term.cpp b/test/term/term.cpp new file mode 100644 index 0000000000..eb4885dd6c --- /dev/null +++ b/test/term/term.cpp @@ -0,0 +1,85 @@ +// This file is a part of the IncludeOS unikernel - www.includeos.org +// +// Copyright 2015 Oslo and Akershus University College of Applied Sciences +// and Alfred Bratterud +// +// 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. + +#include +#include +#include + +#include +std::unique_ptr > inet; + +#include +std::unique_ptr term; + +#include + +void Service::start() +{ + // boilerplate + hw::Nic& eth0 = hw::Dev::eth<0,VirtioNet>(); + inet = std::make_unique >(eth0); + inet->network_config( + {{ 10,0,0,42 }}, // IP + {{ 255,255,255,0 }}, // Netmask + {{ 10,0,0,1 }}, // Gateway + {{ 8,8,8,8 }} ); // DNS + + INFO("TERM", "Running tests for Terminal"); + auto disk = fs::new_shared_memdisk(); + assert(disk); + + // auto-mount filesystem + disk->mount( + [disk] (fs::error_t err) + { + CHECKSERT(!err, "Filesystem auto-mounted"); + + /// terminal /// +#define SERVICE_TELNET 23 + auto& tcp = inet->tcp(); + auto& server = tcp.bind(SERVICE_TELNET); + server.onConnect( + [disk] (auto client) + { + // create terminal with open TCP connection + term = std::make_unique (client); + term->add_disk_commands(disk); + + /// work on network commands /// + // add 'ifconfig' command + term->add( + "ifconfig", "Configure a network interface", + [] (const std::vector&) -> int + { + term->write("Link encap:%s\r\n", inet->tcp().status().c_str()); + return 0; + }); + // add 'netstat' command + term->add( + "netstat", "Print network connections", + [] (const std::vector&) -> int + { + term->write("%s\r\n", inet->tcp().status().c_str()); + return 0; + }); + }); + + INFO("TERM", "Connect to terminal with $ telnet %s ", + inet->ip_addr().str().c_str()); + /// terminal /// + }); +} diff --git a/test/term/test.sh b/test/term/test.sh new file mode 100755 index 0000000000..eae2640310 --- /dev/null +++ b/test/term/test.sh @@ -0,0 +1,19 @@ +#!/bin/bash +source ../test_base +mkdir tmpdisk + +### FAT16 TEST ### +rm -f my.disk +dd if=/dev/zero of=my.disk count=6500 +mkfs.fat my.disk +sudo mount my.disk tmpdisk/ +sudo cp banana.txt tmpdisk/ +sudo mkdir -p tmpdisk/dir1/dir2/dir3 +sync # Mui Importante +sudo umount tmpdisk/ + +make SERVICE=Test DISK=my.disk FILES=term.cpp +start Test.img "Term: Terminal test" +make SERVICE=Test FILES=term.cpp clean +rm -f memdisk.o my.disk +rmdir tmpdisk/ diff --git a/test/timers/Makefile b/test/timers/Makefile index 00349c6380..64f2ad2da1 100644 --- a/test/timers/Makefile +++ b/test/timers/Makefile @@ -3,132 +3,17 @@ ################################################# # The name of your service -SERVICE = Test_timers +SERVICE = test_timers +SERVICE_NAME = Timers Test Service # Your service parts FILES = service.cpp +# Your disk image +DISK= # IncludeOS location ifndef INCLUDEOS_INSTALL - INCLUDEOS_INSTALL=$(HOME)/IncludeOS_install +INCLUDEOS_INSTALL=$(HOME)/IncludeOS_install endif -# Shorter name -INSTALL = $(INCLUDEOS_INSTALL) - -# Compiler/Linker -################################################### - -OPTIONS = -Ofast -msse3 -Wall -Wextra -mstackrealign - -# External Libraries -################################################### -LIBC_OBJ = $(INSTALL)/newlib/libc.a -LIBG_OBJ = $(INSTALL)/newlib/libg.a -LIBM_OBJ = $(INSTALL)/newlib/libm.a - -LIBGCC = $(INSTALL)/libgcc/libgcc.a -LIBCXX = $(INSTALL)/libcxx/libc++.a $(INSTALL)/libcxx/libc++abi.a - - -INC_NEWLIB=$(INSTALL)/newlib/include -INC_LIBCXX=$(INSTALL)/libcxx/include - -DEBUG_OPTS = -ggdb3 -v - -CPP = clang++-3.6 -target i686-elf -LD = ld - -INCLUDES = -I$(INC_LIBCXX) -I$(INSTALL)/api/sys -I$(INC_NEWLIB) -I$(INSTALL)/api - -CAPABS_COMMON = -msse3 -mstackrealign # Needed for 16-byte stack alignment (SSE) - -all: CAPABS = $(CAPABS_COMMON) -O2 -debug: CAPABS = $(CAPABS_COMMON) -O0 -stripped: CAPABS = $(CAPABS_COMMON) -Oz - -WARNS = -Wall -Wextra #-pedantic -CPPOPTS = $(CAPABS) $(WARNS) -c -m32 -std=c++14 -fno-stack-protector $(INCLUDES) -D_LIBCPP_HAS_NO_THREADS=1 #-flto -fno-exceptions - -LDOPTS = -nostdlib -melf_i386 -N --script=$(INSTALL)/linker.ld -flto - - -# Objects -################################################### - -CRTBEGIN_OBJ = $(INSTALL)/crt/crtbegin.o -CRTEND_OBJ = $(INSTALL)/crt/crtend.o -CRTI_OBJ = $(INSTALL)/crt/crti.o -CRTN_OBJ = $(INSTALL)/crt/crtn.o - -# Full link list -OBJS = $(FILES:.cpp=.o) .service_name.o -LIBS = $(INSTALL)/os.a $(LIBCXX) $(INSTALL)/os.a $(LIBC_OBJ) $(LIBM_OBJ) $(LIBGCC) - -OS_PRE = $(CRTBEGIN_OBJ) $(CRTI_OBJ) -OS_POST = $(CRTEND_OBJ) $(CRTN_OBJ) - -DEPS = $(OBJS:.o=.d) - -# Complete bulid -################################################### -# A complete build includes: -# - a "service", to be linked with OS-objects (OS included) - -all: service - -stripped: LDOPTS += -S #strip all -stripped: CPPOPTS += -Oz -stripped: service - - -# The same, but with debugging symbols (OBS: Dramatically increases binary size) -debug: CCOPTS += $(DEBUG_OPTS) -debug: CPPOPTS += $(DEBUG_OPTS) -debug: LDOPTS += -M --verbose - -debug: OBJS += $(LIBG_OBJ) - -debug: service #Don't wanna call 'all', since it strips debug info - -# Service -################################################### -service.o: service.cpp - @echo "\n>> Compiling the service" - $(CPP) $(CPPOPTS) -o $@ $< - -.service_name.o: $(INSTALL)/service_name.cpp - $(CPP) $(CPPOPTS) -DSERVICE_NAME="\"$(SERVICE)\"" -o $@ $< - -# Link the service with the os -service: $(OBJS) $(LIBS) - @echo "\n>> Linking service with OS" - $(LD) $(LDOPTS) $(OS_PRE) $(OBJS) $(LIBS) $(OS_POST) -o $(SERVICE) - @echo "\n>> Building image " $(SERVICE).img - $(INSTALL)/vmbuild $(INSTALL)/bootloader $(SERVICE) - -# Object files -################################################### - -# Runtime -crt%.o: $(INSTALL)/crt/crt%.s - @echo "\n>> Assembling C runtime:" $@ - $(CPP) $(CPPOPTS) -x assembler-with-cpp $< - -# General C++-files to object files -%.o: %.cpp - @echo "\n>> Compiling OS object without header" - $(CPP) $(CPPOPTS) -o $@ $< - -# AS-assembled object files -%.o: %.s - @echo "\n>> Assembling GNU 'as' files" - $(CPP) $(CPPOPTS) -x assembler-with-cpp $< - -# Cleanup -################################################### -clean: - $(RM) $(OBJS) $(DEPS) $(SERVICE) - $(RM) $(SERVICE).img - --include $(DEPS) +include $(INCLUDEOS_INSTALL)/Makeseed diff --git a/test/timers/service.cpp b/test/timers/service.cpp index 8726f3cab8..93be400a82 100644 --- a/test/timers/service.cpp +++ b/test/timers/service.cpp @@ -6,9 +6,9 @@ // 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. @@ -19,54 +19,104 @@ #include #include #include +#include using namespace std::chrono; +int one_shots = 0; +int repeat1 = 0; +int repeat2 = 0; + +auto& timer = hw::PIT::instance(); + void Service::start() { - - std::vector integers={1,2,3}; - - printf("TESTING Timers \n"); - auto& time = hw::PIT::instance(); - - - int x = 42; - - // Write something in a while - time.onTimeout(7s, [x](){ printf("7 seconds passed...%i \n",x); }); - - // Write a dot in a second - time.onTimeout(1s, [](){ printf("One second passed...\n"); }); - - // Write something in half a second - time.onTimeout(500ms, [](){ printf("Half a second passed...\n"); }); - - - time.onTimeout(3s, [](){ printf("Three second passed...\n"); }); - - + + + INFO("Test Timers","Testing one-shot timers"); + + + int t1 = 0; + int t10 = 0; + int t2 = 0; + + // 30 sec. - Test End + timer.onTimeout(30s, [] { + printf("One-shots fired: %i \n", one_shots); + CHECKSERT(one_shots == 5, "5 one-shot-timers fired"); + CHECKSERT(repeat1 == 25 and repeat2 == 10, "1s. timer fired 25 times, 2s. timer fired 10 times"); + CHECKSERT(timer.active_timers() == 1, "This is the last active timer"); + INFO("Test Timers","SUCCESS"); + }); + + // 5 sec. + timer.onTimeout(5s, [] { + CHECKSERT(one_shots == 3, + "After 5 sec, 3 other one-shot-timers have fired"); + one_shots++; + }); + + // 0.5 sec. + timer.onTimeout(500ms, [] { + CHECKSERT(one_shots == 0, "After 0.5 sec, no other one-shot-timers have fired"); + one_shots++; + }); + + // 1 sec. + timer.on_timeout(1, [] { + CHECKSERT(one_shots == 1, "After 1 sec, 1 other one-shot-timers has fired"); + one_shots++; + }); + + // You can also use the std::function interface (which is great) - std::function in_a_second = [integers](){ - std::cout << "Hey - this is a std::function - it knows about integers:" << std::endl; + std::vector integers={1,2,3}; + + std::function in_a_second = [integers](){ for (auto i : integers) - std::cout << i << std::endl; + CHECKSERT(i == integers[i - 1], "%i == integers[%i - 1]", i, integers[i - 1]); + one_shots++; }; - - time.onTimeout(1s, in_a_second); - - time.onRepeatedTimeout(1s, []{ printf("1sec. PULSE \n"); }); - time.onRepeatedTimeout(2s, []{ printf("2sec. PULSE, "); }, - - // A continue-condition. The timer stops when false is returned - []{ - static int i = 0; i++; - printf("%i / 10 times \n", i); - if (i >= 10) { - printf("2sec. pulse DONE!"); - return false; - } - return true; }); - - + + timer.onTimeout(1s, in_a_second); + + auto timer1s = timer.onRepeatedTimeout(1s, []{ + repeat1++; + printf("1s. PULSE #%i \n", repeat1); + }); + + timer.onRepeatedTimeout(2s, []{ + repeat2++; + printf("2s. PULSE #%i \n", repeat2); + }, + + // A continue-condition. The timer stops when false is returned + []{ + CHECKSERT(repeat1 == (repeat2 * 2), "2s timer fired %i, 1s fired %i x 2 == %i times", repeat2, repeat2, repeat1); + if (repeat2 >= 10) { + CHECK(true, "2sec. pulse DONE!"); + return false; + } + return true; + }); + + + // 25 sec. - end last repeating timer + timer.onTimeout(25s + 10ms, [ timer1s ] { + one_shots++; + CHECKSERT(repeat1 == 25 and repeat2 == 10, + "After 25 sec, 1s timer x 30 == %i times, 2s timer x 15 == %i times", + repeat1, repeat2); + + timer.stop_timer(timer1s); + CHECKSERT(timer.active_timers() == 2, "There are now 2 timers left"); + + timer.onTimeout(1s, []{ + CHECKSERT(1, "Timers are still functioning"); + }); + + }); + + + } diff --git a/test/timers/test.sh b/test/timers/test.sh new file mode 100755 index 0000000000..a878e3f600 --- /dev/null +++ b/test/timers/test.sh @@ -0,0 +1,5 @@ +#!/bin/bash +source ../test_base + +make +start test_timers.img diff --git a/test/transmit/Makefile b/test/transmit/Makefile new file mode 100644 index 0000000000..1e647c76cb --- /dev/null +++ b/test/transmit/Makefile @@ -0,0 +1,20 @@ +################################################# +# IncludeOS SERVICE makefile # +################################################# + +# The name of your service +SERVICE = test_transmit +SERVICE_NAME = Network transmission tests + +# Your service parts +FILES = service.cpp + +# Your disk image +DISK= + +# IncludeOS location +ifndef INCLUDEOS_INSTALL +INCLUDEOS_INSTALL=$(HOME)/IncludeOS_install +endif + +include $(INCLUDEOS_INSTALL)/Makeseed diff --git a/test/transmit/README.md b/test/transmit/README.md new file mode 100644 index 0000000000..5ad27829e7 --- /dev/null +++ b/test/transmit/README.md @@ -0,0 +1,5 @@ +# Test packet transmission, buffering and transmit-buffers available event + +This test will try to generate and transmit packets as fast as possible, which should now not cause panic, but rather buffering. + +*NOTE: This is a Work In Progress* diff --git a/test/transmit/run.sh b/test/transmit/run.sh new file mode 100755 index 0000000000..3b2132d2cb --- /dev/null +++ b/test/transmit/run.sh @@ -0,0 +1,3 @@ +#! /bin/bash +source ${INCLUDEOS_HOME-$HOME/IncludeOS_install}/etc/run.sh + diff --git a/test/transmit/service.cpp b/test/transmit/service.cpp new file mode 100644 index 0000000000..7e9a115da1 --- /dev/null +++ b/test/transmit/service.cpp @@ -0,0 +1,118 @@ +// This file is a part of the IncludeOS unikernel - www.includeos.org +// +// Copyright 2015 Oslo and Akershus University College of Applied Sciences +// and Alfred Bratterud +// +// 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 DEBUG // Debug supression + +#include +#include +#include +#include + +using namespace std; +using namespace net; + +std::shared_ptr > inet; + +void Service::start() +{ + // Assign an IP-address, using HĂ„rek-mapping :-) + auto& eth0 = hw::Dev::eth<0,VirtioNet>(); + auto& mac = eth0.mac(); + + inet = std::make_shared>(eth0); + auto& timer = hw::PIT::instance(); + + inet->network_config({ mac.part[2],mac.part[3],mac.part[4],mac.part[5] }, + { 255,255,0,0 }, // Netmask + { 10,0,0,1 }, // Gateway + { 8,8,8,8 }); // DNS + + printf("Service IP address: %s \n", inet->ip_addr().str().c_str()); + + // UDP + UDP::port_t port = 4242; + auto& conn = inet->udp().bind(port); + + conn.on_read([&] (UDP::addr_t addr, UDP::port_t port, const char* data, int len) { + string received = std::string(data,len); + + if (received == "SUCCESS") { + INFO("Test 2", "Client says SUCCESS"); + return; + } + + INFO("Test 2","Starting UDP-test. Got UDP data from %s: %i: %s", + addr.str().c_str(), port, received.c_str()); + + const int packets { 2400 }; + + string first_reply {string("Received '") + received + + "'. Expect " + to_string(packets) + " packets in 1s\n" }; + + // Send the first packet, and then wait for ARP + conn.sendto(addr, port, first_reply.c_str(), first_reply.size()); + + timer.onTimeout(1s, [&conn, addr, port, data, len]() { + + auto bufcount = inet->buffers_available(); + size_t packetsize = inet->ip_obj().MDDS() - sizeof(UDP::udp_header); + INFO("Test 2", "Trying to transmit %i UDP packets of size %i at maximum throttle", + packets, packetsize); + + for (int i = 0; i < packets; i++) { + char c = (char)('A' + (i % 26)); + string send (packetsize, c); + //printf(" %i nr. of %c \n", send.size(), c); + conn.sendto(addr, port, send.c_str() , send.size()); + } + + CHECK(1,"UDP-transmission didn't panic"); + auto bufcount2 = inet->buffers_available(); + + INFO("UDP Transmision tests","OK"); + }); + }); + + + /* + WARNING: Subscribing to this event will overwrite the Inet delegate. So Don't + + eth0.on_transmit_queue_available([](size_t s){ + CHECKSERT(s,"There is now room for %i packets in transmit queue", s); + }); */ + + timer.onTimeout(200ms,[=](){ + const int packets { 600 }; + INFO("Test 1", "Trying to transmit %i ethernet packets at maximum throttle", packets); + for (int i=0; i < packets; i++){ + auto pckt = inet->createPacket(inet->MTU()); + Ethernet::header* hdr = reinterpret_cast(pckt->buffer()); + hdr->dest.major = Ethernet::addr::BROADCAST_FRAME.major; + hdr->dest.minor = Ethernet::addr::BROADCAST_FRAME.minor; + hdr->type = Ethernet::ETH_ARP; + inet->link().transmit(pckt); + } + + CHECK(1,"Transmission didn't panic"); + INFO("Test 1", "Done. Send some UDP-data to %s:%i to continue test", + inet->ip_addr().str().c_str(), port); + + + }); + + +} diff --git a/test/transmit/test.py b/test/transmit/test.py new file mode 100755 index 0000000000..c0d8038d2d --- /dev/null +++ b/test/transmit/test.py @@ -0,0 +1,83 @@ +#! /usr/bin/python +import sys +sys.path.insert(0,"..") + +import vmrunner +import socket +import time + +HOST, PORT = "10.0.0.42", 4242 +# SOCK_DGRAM is the socket type to use for UDP sockets +sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + +# Get an auto-created VM from the vmrunner +vm = vmrunner.vms[0] + + +packet_content = True +def assert_packet_content(test): + if not test: + packet_content = False + print " FAIL: Packet content is wrong" + return test + +packet_order = True +def assert_packet_order(test): + if not test: + packet_order = False + print " FAIL: Packet order is wrong" + return test + +def UDP_test(): + print " Performing UDP test 1" + + data = "Douche" + sock.sendto(data+"\n", (HOST, PORT)) + received = sock.recv(100) + + print " Sent: {}".format(data) + print " Received: {}".format(received) + + expect = "A" + + packet_size = 1472 + packet_count = 2400 + + OK = False + i = 1 + bytes_received = 0; + + t1 = time.clock() + + while not vm.poll(): + received = sock.recv(65000) + bytes_received += len(received) + first_char = received[0] + last_char = received[len(received)-1] + #print "Packet",i,len(received),"bytes, (",bytes_received," total)",first_char, " - ", last_char + + if not assert_packet_content(first_char == last_char): return False + if not assert_packet_order(ord(first_char) == ord(expect)): return False + + # Make sure the chars in this packets were incremented or wrapped around + if first_char == 'Z' : expect = "A" + else : expect = chr(ord(first_char) + 1) + i += 1 + + if (bytes_received >= packet_count * packet_size): + break + + time_taken = time.clock() - t1 + print " Transmission finished in ", time_taken + + if packet_order and packet_content: + sock.sendto("SUCCESS", (HOST, PORT)) + + return packet_order and packet_content + + +# Add custom event-handler +vm.on_output("Done. Send some UDP-data", UDP_test) + +# Boot the VM, taking a timeout as parameter +vm.boot(20) diff --git a/test/transmit/vm.json b/test/transmit/vm.json new file mode 100644 index 0000000000..24a071ca9f --- /dev/null +++ b/test/transmit/vm.json @@ -0,0 +1,8 @@ +{ + "image" : "test_transmit.img", + "net" : [{"type" : "virtio", + "mac" : "c0:01:0a:00:00:2a", + "log" : "net_result.pcap"}], + "cpu" : "host", + "mem" : 256 +} diff --git a/test/validate_all.sh b/test/validate_all.sh new file mode 100755 index 0000000000..620f15de59 --- /dev/null +++ b/test/validate_all.sh @@ -0,0 +1,8 @@ +#! /bin/bash +if [ "$1" == "-onlyNames" ] + then + for t in `ls -d */`; do ./validate_test.py $t | grep PASS | rev | cut -d "/" -f1 | rev; done + exit +fi + +for t in `ls -d */`; do ./validate_test.py $t; done diff --git a/test/validate_test.py b/test/validate_test.py new file mode 100755 index 0000000000..e31868daa2 --- /dev/null +++ b/test/validate_test.py @@ -0,0 +1,59 @@ +#! /usr/bin/python +import jsonschema +import json +import sys +import os +import glob + +vm_schema = None +jsons = [] +valid_vms = [] + +def load_schema(filename): + global vm_schema + vm_schema = json.loads(open(filename).read()); + +def validate_vm_spec(filename): + + global valid_vms + vm_spec = None + + # Load and parse as JSON + try: + vm_spec = json.loads(open(filename).read()) + except: + raise Exception("JSON load / parse Error for " + filename) + + # Validate JSON according to schema + try: + jsonschema.validate(vm_spec, vm_schema) + except Exception as err: + raise Exception("JSON schema validation failed: " + err.message) + + valid_vms.append(vm_spec) + + +def has_required_stuff(path): + + global jsons + + # Certain files are mandatory + required_files = [ "Makefile", "test.py", "README.md", "*.cpp" ] + for file in required_files: + if not glob.glob(file): + raise Exception("missing " + file) + + # JSON-files must conform to VM-schema + jsons = glob.glob("*.json") + for json in jsons: + validate_vm_spec(json) + +if __name__ == "__main__": + path = sys.argv[1] if len(sys.argv) > 1 else "." + load_schema("vm.schema.json") + os.chdir(path) + try: + has_required_stuff(path) + print " \tPASS: ",os.getcwd() + except Exception as err: + print " \tFAIL: unmet requirements in " + path, ": " , err.message diff --git a/test/vga/Makefile b/test/vga/Makefile new file mode 100644 index 0000000000..92d5beedee --- /dev/null +++ b/test/vga/Makefile @@ -0,0 +1,19 @@ +################################################# +# IncludeOS SERVICE makefile # +################################################# + +# The name of your service +SERVICE = Test +SERVICE_NAME = The VGA Test Service + +# Your service parts +FILES=vga.cpp +# Your disk image +DISK= + +# IncludeOS location +ifndef INCLUDEOS_INSTALL +INCLUDEOS_INSTALL=$(HOME)/IncludeOS_install +endif + +include $(INCLUDEOS_INSTALL)/Makeseed diff --git a/test/vga/debug.sh b/test/vga/debug.sh new file mode 100755 index 0000000000..f55e4c7471 --- /dev/null +++ b/test/vga/debug.sh @@ -0,0 +1,2 @@ +#!/bin/bash +gdb -x service.gdb diff --git a/test/vga/run.sh b/test/vga/run.sh new file mode 100755 index 0000000000..587d9bcf8a --- /dev/null +++ b/test/vga/run.sh @@ -0,0 +1,3 @@ +#! /bin/bash +export DEV_GRAPHICS="-vga std" +source ${INCLUDEOS_HOME-$HOME/IncludeOS_install}/etc/run.sh diff --git a/test/vga/service.gdb b/test/vga/service.gdb new file mode 100644 index 0000000000..6c699abfe8 --- /dev/null +++ b/test/vga/service.gdb @@ -0,0 +1,3 @@ +file Test +break Service::start +target remote localhost:1234 diff --git a/test/vga/test.sh b/test/vga/test.sh new file mode 100755 index 0000000000..abfb1f6c33 --- /dev/null +++ b/test/vga/test.sh @@ -0,0 +1,7 @@ +#!/bin/bash +source ../test_base + +export DEV_GRAPHICS="-vga std" +make SERVICE=Test FILES=vga.cpp +start Test.img "VGA: Verify that the service starts test" +make SERVICE=Test FILES=vga.cpp clean diff --git a/test/vga/vbox.sh b/test/vga/vbox.sh new file mode 100755 index 0000000000..3ce34897a7 --- /dev/null +++ b/test/vga/vbox.sh @@ -0,0 +1,2 @@ +#!/bin/bash +../../etc/vboxrun.sh Test.img diff --git a/test/vga/vga.cpp b/test/vga/vga.cpp new file mode 100644 index 0000000000..c098599bff --- /dev/null +++ b/test/vga/vga.cpp @@ -0,0 +1,114 @@ +// This file is a part of the IncludeOS unikernel - www.includeos.org +// +// Copyright 2015 Oslo and Akershus University College of Applied Sciences +// and Alfred Bratterud +// +// 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. + +#include +#include +#include +#include +ConsoleVGA vga; + +static char c = '0'; +static int iterations = 0; + +using namespace std::chrono; + +void write_goodbye(){ + + char msg[] = {0xd,0xe,' ','G','A','M','E',' ','O','V','E','R',' ',0xe,0xd,0}; + vga.setCursorAt(32,24); + vga.setColor(ConsoleVGA::COLOR_LIGHT_BROWN); + vga.write(msg, sizeof(msg)); + +} + +void Service::start() +{ + INFO("VGA", "Running tests for VGA"); + + OS::set_rsprint([] (const char* data, size_t len) { + vga.write(data, len); + }); + + printf("Hello there!\n"); + + auto test1 = [](){ + vga.putEntryAt('@',0,0); + vga.putEntryAt('@',0,24); + vga.putEntryAt('@',79,0); + vga.putEntryAt('@',79,24); + + static int row = 0; + static int col = 0; + + vga.setColor(col % 256); + vga.putEntryAt(c,col % 80, row % 25); + + if (col++ % 80 == 79){ + row++; + } + + if (row % 25 == 24 and col % 80 == 79) + c++; + }; + + + auto test1_1 = [] () -> bool + { + if ( c >= '4') { + hw::PIT::instance().onRepeatedTimeout(100ms, [] { + vga.newline(); + iterations++; + if (iterations == 24) + write_goodbye(); + + }, + + [] { + return iterations < 36; + }); + } + return c < '4'; + }; + + + auto test2 = [](){ + const int width = 40; + + char buf[width]; + for (int i = 0; i + +#include +std::shared_ptr disk; + +void list_partitions(decltype(disk)); + +#define MYINFO(X,...) INFO("VirtioBlk",X,##__VA_ARGS__) + +void Service::start() +{ + // instantiate memdisk with FAT filesystem + auto& device = hw::Dev::disk<1, VirtioBlk>(); + disk = std::make_shared (device); + // assert that we have a disk + CHECKSERT(disk, "Disk created"); + // if the disk is empty, we can't mount a filesystem + CHECKSERT(not disk->empty(), "Disk is not empty"); + + // list extended partitions + list_partitions(disk); + + // mount first valid partition (auto-detect and mount) + disk->mount( + [] (fs::error_t err) { + if (err) { + printf("Could not mount filesystem\n"); + panic("mount() failed"); + } + + // async ls + disk->fs().ls("/", + [] (fs::error_t err, auto ents) { + if (err) { + printf("Could not list '/' directory\n"); + panic("ls() failed"); + } + + // go through directory entries + for (auto& e : *ents) { + printf("%s: %s\t of size %llu bytes (CL: %llu)\n", + e.type_string().c_str(), e.name().c_str(), e.size(), e.block); + + if (e.is_file()) { + printf("*** Read %s\n", e.name().c_str()); + disk->fs().read(e, 0, e.size(), + [e] (fs::error_t err, fs::buffer_t buffer, size_t len) { + if (err) { + printf("Failed to read %s!\n", e.name().c_str()); + panic("read() failed"); + } + + std::string contents((const char*) buffer.get(), len); + printf("[%s contents]:\n%s\nEOF\n\n", + e.name().c_str(), contents.c_str()); + // --- + INFO("Virtioblk Test", "SUCCESS"); + }); + + } // is_file + + } // ents + + }); // ls + + }); // disk->auto_detect() + + printf("*** TEST SERVICE STARTED *** \n"); + +} + +void list_partitions(decltype(disk) disk) +{ + disk->partitions([] (fs::error_t err, auto& parts) { + + CHECKSERT (not err, "Was able to fetch partition table"); + + for (auto& part : parts) + printf("[Partition] '%s' at LBA %u\n", + part.name().c_str(), part.lba()); + + }); +} diff --git a/test/virtio_block/test.py b/test/virtio_block/test.py new file mode 100755 index 0000000000..e21ab8f379 --- /dev/null +++ b/test/virtio_block/test.py @@ -0,0 +1,22 @@ +#! /usr/bin/python +import sys +import subprocess + +sys.path.insert(0,"..") +subprocess.call(['./image.sh']) + +def cleanup(): + subprocess.call(['./cleanup.sh']) + +def success(): + print "FGERSGERGER" + cleanup() + exit(0, " SUCCESS : All tests passed") +def failure(): + cleanup() + exit(66, " FAIL : what happen") + +import vmrunner +vmrunner.vms[0].on_success(success) +vmrunner.vms[0].on_panic(failure) +vmrunner.vms[0].boot() diff --git a/test/virtio_block/test.sh b/test/virtio_block/test.sh new file mode 100755 index 0000000000..7378f9b2ee --- /dev/null +++ b/test/virtio_block/test.sh @@ -0,0 +1,6 @@ +#!/bin/bash +source ../test_base + +make +export HDB="-drive file=./test.txt,if=virtio,media=disk" +start test_virtio_block.img diff --git a/test/virtio_block/test.txt b/test/virtio_block/test.txt new file mode 100644 index 0000000000..3ed50e9af6 --- /dev/null +++ b/test/virtio_block/test.txt @@ -0,0 +1,22 @@ + + ,----. ,-. ,----.,------. ,-. ,-.,-. ,-. + / ,-,_/ ,' | / /"P /`-, ,-',' | / // |/ / + / / __ ,' ,| | / ,---' / / ,' ,| | / // J P / + / '-' /,' ,--. |/ / / /,' ,--. |/ // /| / + `----''--' `-'`'.--""""--.--' `-'`' `' `-' + nnnnnnnnnnnnnnnn,'.n*""""*N.`.####################### + NNNNNNNNNNNNNNN/ J',n*""*n.`L \##### ### ### ### #### + : J J___/\___L L :##################### + nnnnnnnnnnnnnn{ [{ `. ,' }] }## ### ### ### ### ## + NNNNNNNNNNNNNN: T T /,'`.\ T J :##################### + \ L,`*n,,n*',J / + nnnnnnnnnnnnnnnn`. *n,,,,n* ,'nnnnnnnnnnnnnnnnnnnnnnn + NNNNNNNNNNNNNNNNNN`-..__..-'NNNNNNNNNNNNNNNNNNNNNNNNN + ,-. ,-. ,-. ,----. ,----.,-. ,----. ,-. + | `. \ `.| \\ .--`\ \"L \\ \\ .-._\ | `. o!0 + | |. `. \ \ ` L \\ __\ \ . < \ \\ \ __ | |. `. + | .--. `.\ \`-'\ \\ `---.\ \L `.\ \\ `-` \| .--. `. + `-' `--``' `-'`----' `-'`-' `' `----'`-' `--' + +####################################################### +##IncludeOS Rocks! ## diff --git a/test/virtio_block/vm.json b/test/virtio_block/vm.json new file mode 100644 index 0000000000..7c10b1a08f --- /dev/null +++ b/test/virtio_block/vm.json @@ -0,0 +1,6 @@ +{ + "image" : "test_virtio_block.img", + "drives" : [{"file" : "image.img", + "type" : "virtio", + "format": "raw" }] +} diff --git a/test/virtio_queue/Makefile b/test/virtio_queue/Makefile new file mode 100644 index 0000000000..e9f2a091ba --- /dev/null +++ b/test/virtio_queue/Makefile @@ -0,0 +1,24 @@ +################################################# +# IncludeOS SERVICE makefile # +################################################# + +# The name of your service +SERVICE = test_virtio_queue +SERVICE_NAME = Virtio Queue Test + +# Your service parts +FILES = service.cpp + +LOCAL_INCLUDES=-I$(PWD)/../lest/include/lest -I$(PWD)/../../mod/GSL/include + +# Your disk image +DISK= + + + +# IncludeOS location +ifndef INCLUDEOS_INSTALL +INCLUDEOS_INSTALL=$(HOME)/IncludeOS_install +endif + +include $(INCLUDEOS_INSTALL)/Makeseed diff --git a/test/virtio_queue/README.md b/test/virtio_queue/README.md new file mode 100644 index 0000000000..e4ff29ef50 --- /dev/null +++ b/test/virtio_queue/README.md @@ -0,0 +1,6 @@ +# Test Virtio::Queue. WIP + +Sucess: Outputs SUCCESS if all tests pass +Fail: Panic if any test fails + +NOTE: This test uses the `lest` unit test framework as well for some tests. The result of those tests will also be reported. diff --git a/test/virtio_queue/run.sh b/test/virtio_queue/run.sh new file mode 100755 index 0000000000..3b2132d2cb --- /dev/null +++ b/test/virtio_queue/run.sh @@ -0,0 +1,3 @@ +#! /bin/bash +source ${INCLUDEOS_HOME-$HOME/IncludeOS_install}/etc/run.sh + diff --git a/test/virtio_queue/service.cpp b/test/virtio_queue/service.cpp new file mode 100644 index 0000000000..eeb11875a7 --- /dev/null +++ b/test/virtio_queue/service.cpp @@ -0,0 +1,95 @@ +// This file is a part of the IncludeOS unikernel - www.includeos.org +// +// Copyright 2015 Oslo and Akershus University College of Applied Sciences +// and Alfred Bratterud +// +// 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. + +/** + A very superficial test to verify that basic STL is working + This is useful when we mess with / replace STL implementations + +**/ + + +#include + +#include +#include +#include + +int clock_gettime(clockid_t clk_id, struct timespec *tp){ + (void*)clk_id; + (void*)tp; + return 0; +}; + +using namespace std; + +const lest::test virtio_tests[] = { + { + + + SCENARIO( "Virtio Queues work" ) + { + const int queue_size = 256; + + GIVEN( "A virtio queue with 10 tokens" ) { + Virtio::Queue vq(queue_size, 0, 0); + enum Direction { IN, OUT }; + + EXPECT (vq.num_free() == queue_size); + + WHEN ("You enqueue 10 tokens") { + + for (int i = 0; i < 10; i++) { + Virtio::Token token {(uint8_t*) malloc(100), 100 }; + vq.enqueue(token , Virtio::Queue::Direction::OUT); + printf("Free tokens: %i \n", vq.num_free()); + } + + EXPECT (vq.num_free() == (queue_size - 10)); + + THEN("You dequeue 10 tokens") { + uint32_t res; + for (int i = 0; i < 10; i++) + vq.dequeue(&res); + + EXPECT (vq.num_free() == 0); + } + } + + WHEN ("You insert too many tokens") { + + for (int i = 0; i < queue_size * 3; i++) { + Virtio::Token token {(uint8_t*) malloc(100), 100 }; + vq.enqueue(token, Virtio::Queue::Direction::OUT); + } + + THEN ("Queue reports 10 items") { + EXPECT (vq.num_free() == (queue_size - 10)); + } + } + } + } + } +}; + +#define MYINFO(X,...) INFO("Test STL",X,##__VA_ARGS__) + +void Service::start() +{ + + CHECK( lest::run(virtio_tests, {"-p"}) == 0, "All tests passed" ); + MYINFO("SUCCESS"); +} diff --git a/test/virtio_queue/test.sh b/test/virtio_queue/test.sh new file mode 100755 index 0000000000..c663df1801 --- /dev/null +++ b/test/virtio_queue/test.sh @@ -0,0 +1,9 @@ +#!/bin/bash +source ../test_base + +make +start test_virtio_queue.img + +cd ../tcp +make clean && make +start test_tcp.img diff --git a/test/vm.schema.json b/test/vm.schema.json new file mode 100644 index 0000000000..a9753455ef --- /dev/null +++ b/test/vm.schema.json @@ -0,0 +1,69 @@ +{ + "$schema": "http://json-schema.org/schema#", + "title" : "Virtual Machine Image", + "type" : "object", + "properties" : { + + "image" : { + + "description" : "A bootable virtual machine image", + "type" : "string", + "default" : "service.img" + }, + + "drives" : { + + "description" : "Additional virtual hard drives", + "type" : "array", + "items" : { + "type" : "object", + "properties" : { + "file" : { "type" : "string" }, + "type" : { "enum" : ["ide", "virtio"] }, + "format" : { "enum" : ["raw", "qcow2", "vdi"] }, + "name" : { "type" : "string" } + }, + + "required" : ["file", "format", "type"] + } + }, + + "net" : { + + "description" : "Network devices", + "type" : "array", + "items" : { + "type" : "object", + "properties" : { + "type" : { "enum" : ["virtio"] }, + "name" : { "type" : "string" } + }, + + "required" : ["type"] + } + }, + + + "mem" : { + + "description" : "Amount of memory in megabytes", + "type" : "number", + "default" : 128 + }, + + "cpu" : { + + "description" : "The virtual CPU", + "enum" : ["host", "pentium"] + }, + + + "smp" : { + + "description" : "Number of virtual CPU's", + "type" : "number" + } + }, + + "required" : ["image"] +} diff --git a/test/vmrunner.py b/test/vmrunner.py new file mode 100644 index 0000000000..38c770fa23 --- /dev/null +++ b/test/vmrunner.py @@ -0,0 +1,276 @@ +print "vmrunner.py running" + +import os +import sys +import subprocess +import threading +import time +print os.getcwd() +import re + +import validate_test + +INCLUDEOS_HOME = None + +if not os.environ.has_key("INCLUDEOS_HOME"): + print "WARNING: Environment varialble INCLUDEOS_HOME is not set. Trying default" + def_home = os.environ["HOME"] + "/IncludeOS_install" + if not os.path.isdir(def_home): raise Exception("Couldn't find INCLUDEOS_HOME") + INCLUDEOS_HOME= def_home +else: + INCLUDEOS_HOME = os.environ['INCLUDEOS_HOME'] + +# Exit codes used by this program +exit_codes = {"SUCCESS" : 0, + "PROGRAM_FAILURE" : 1, + "TIMEOUT" : 66, + "VM_FAIL" : 67, + "OUTSIDE_FAIL" : 68 } + + +def abstract(): + raise Exception("Abstract class method called. Use a subclass") +# Hypervisor base / super class +# (It seems to be recommended for "new style classes" to inherit object) +class hypervisor(object): + + def __init__(self, config): + self._config = config; + + # Boot a VM, returning a hypervisor handle for reuse + def boot(self): + abstract() + + # Stop the VM booted by boot + def stop(self): + abstract() + + # Read a line of output from vm + def readline(self): + abstract() + + # Verify that the hypervisor is available + def available(self, config_data = None): + abstract() + + # Wait for this VM to exit + def wait(self): + abstract() + + # Wait for this VM to exit + def poll(self): + abstract() + +# Start a process we expect to not finish immediately (i.e. a VM) +def start_process(popen_param_list): + # Start a subprocess + proc = subprocess.Popen(popen_param_list, + stdout = subprocess.PIPE, + stderr = subprocess.PIPE) + + # After half a second it should be started, otherwise throw + time.sleep(0.5) + if (proc.poll()): + data, err = proc.communicate() + raise Exception("Process exited. ERROR: " + err + " " + data); + + print " Started process PID ",proc.pid + return proc + + +# Qemu Hypervisor interface +class qemu(hypervisor): + + def drive_arg(self, filename, drive_type="virtio", drive_format="raw"): + return ["-drive","file="+filename+",format="+drive_format+",if="+drive_type] + + def net_arg(self, if_type = "virtio", if_name = "net0", mac="c0:01:0a:00:00:2a"): + type_names = {"virtio" : "virtio-net"} + qemu_ifup = INCLUDEOS_HOME+"/etc/qemu-ifup" + return ["-device", type_names[if_type]+",netdev="+if_name+",mac="+mac, + "-netdev", "tap,id="+if_name+",script="+qemu_ifup] + + def kvm_present(self): + command = "egrep -m 1 '^flags.*(vmx|svm)' /proc/cpuinfo" + try: + subprocess.check_output(command, shell = True) + print " KVM ON" + return True + + except Exception as err: + print " KVM OFF" + return False + + def boot(self): + self._out_sign = "<" + type(self).__name__ + ">" + print self._out_sign,"booting ",self._config["image"] + + disk_args = self.drive_arg(self._config["image"], "ide") + if self._config.has_key("drives"): + for disk in self._config["drives"]: + disk_args += self.drive_arg(disk["file"], disk["type"], disk["format"]) + + net_args = [] + i = 0 + if self._config.has_key("net"): + for net in self._config["net"]: + net_args += self.net_arg(net["type"], "net"+str(i), net["mac"]) + i+=1 + + command = ["sudo", "qemu-system-x86_64"] + if self.kvm_present(): command.append("--enable-kvm") + + command += ["-nographic" ] + disk_args + net_args + + print self._out_sign, "command:", command + + self._proc = start_process(command) + + def stop(self): + if hasattr(self, "_proc") and self._proc.poll() == None : + print self._out_sign,"Stopping", self._config["image"], "PID",self._proc.pid + # Kill with sudo + subprocess.check_call(["sudo","kill", "-SIGTERM", str(self._proc.pid)]) + # Wait for termination (avoids the need to reset the terminal) + self._proc.wait() + return self + + def wait(self): + print self._out_sign, "Waiting for process to terminate" + self._proc.wait() + + def readline(self): + if self._proc.poll(): + raise Exception("Process completed") + return self._proc.stdout.readline() + + def poll(self): + return self._proc.poll() + +# VM class +class vm: + + + def __init__(self, config, hyper = qemu): + self._exit_status = 0 + self._config = config + self._on_success = lambda : self.exit(exit_codes["SUCCESS"], " SUCCESS : All tests passed") + self._on_panic = lambda : self.exit(exit_codes["VM_FAIL"], " FAIL : " + self._hyper.readline()) + self._on_timeout = lambda : self.exit(exit_codes["TIMEOUT"], " TIMEOUT: Test timed out") + self._on_output = { + "PANIC" : self._on_panic, + "SUCCESS" : self._on_success } + assert(issubclass(hyper, hypervisor)) + self._hyper = hyper(config) + self._timer = None + + def exit(self, status, msg): + self.stop() + print + print msg + self._exit_status = status + # sys.exit won't really do anything if called from a (timer) thread + sys.exit(status) + + def on_output(self, output, callback): + self._on_output[ output ] = callback + + def on_success(self, callback): + self._on_success = callback + + def on_panic(self, callback): + self._on_panic = callback + + def on_timeout(self, callback): + self._on_timeout = callback + + def readline(self): + return self._hyper.readline() + + def boot(self, timeout = None): + + # Start the timeout thread + if (timeout): + self._timer = threading.Timer(timeout, self._on_timeout) + self._timer.start() + + # Boot via hypervisor + try: + self._hyper.boot() + except Exception as err: + if (timeout): self._timer.cancel() + raise err + #self.exit(1, err) + + # Start analyzing output + while self._hyper.poll() == None and not self._exit_status: + line = self._hyper.readline() + print "",line.rstrip() + # Look for event-triggers + for pattern, func in self._on_output.iteritems(): + if re.search(pattern, line): + res = func() + #NOTE: It can be 'None' without problem + if res == False: + self._exit_status = exit_codes["OUTSIDE_FAIL"] + self.exit(self._exit_status, " External test failed") + print " VM-external test failed" + + + + # Now we either have an exit status from timer thread, or an exit status + # from the subprocess. + self.stop() + self.wait() + + if self._exit_status: + print " Done running VM. Exit status: ", self._exit_status + sys.exit(self._exit_status) + else: + print " Subprocess finished. Exiting with ", self._hyper.poll() + sys.exit(self._hyper.poll()) + + raise Exception("Unexpected termination") + + def stop(self): + self._hyper.stop() + if hasattr(self, "_timer") and self._timer: + self._timer.cancel() + return self + + def wait(self): + if hasattr(self, "_timer") and self._timer: + self._timer.join() + self._hyper.wait() + return self._exit_status + + + def poll(self): + return self._hyper.poll() + + +print +print "", "Validating test" + +validate_test.load_schema("../vm.schema.json") +validate_test.has_required_stuff(".") + +print +print "", "Building test with 'make'" +subprocess.check_call(["make"]) + +default_spec = {"image" : "test.img"} + +# Provide a list of VM's with validated specs +vms = [] + +if validate_test.valid_vms: + print + print "", "Loaded VM specification(s) from JSON" + for spec in validate_test.valid_vms: + vms.append(vm(spec)) + +else: + print + print "", "No VM specification JSON found, trying default: ", default_spec + vms.append(vm(default_spec)) diff --git a/vmbuild/Makefile b/vmbuild/Makefile index db9281620e..7fc445c93f 100644 --- a/vmbuild/Makefile +++ b/vmbuild/Makefile @@ -1,4 +1,4 @@ -CPP=clang++-3.6 +CPP=clang++-3.8 CPPOPTS = -std=c++11 -c -Wall -Wextra -O3 -I. OBJS = vmbuild.o OUT = vmbuild @@ -13,6 +13,3 @@ all: $(OBJS) clean: $(RM) $(OBJS) $(OUT) *~ - - - diff --git a/vmbuild/vmbuild.cpp b/vmbuild/vmbuild.cpp index 454b13672c..9e5fd7cb82 100644 --- a/vmbuild/vmbuild.cpp +++ b/vmbuild/vmbuild.cpp @@ -45,13 +45,14 @@ int main(int argc, char** argv) { // Verify proper command usage if (argc < 3) { cout << info << usage; - exit(0x00); + exit(EXIT_FAILURE); } const string bootloc {argv[1]}; const string srvloc {argv[2]}; - const int extra_sectors {1}; //< Fixes missing Magic Signature :bug: + // Fixes missing Magic Signature :bug: + const int extra_sectors = 2; const string img_name {srvloc.substr(srvloc.find_last_of("/") + 1, string::npos) + ".img"}; @@ -106,13 +107,13 @@ int main(int argc, char** argv) { // Bochs requires old-school disk specifications. // sectors=cyls*heads*spt (sectors per track) /* - const int spt = 63; - auto disk_tracks = - (img_size_sect % spt) == 0 ? - (img_size_sect / spt) : // Sector count is a multiple of 63 - (img_size_sect / spt) + 1; // There's a remainder, so we add one track + const int spt = 63; + auto disk_tracks = + (img_size_sect % spt) == 0 ? + (img_size_sect / spt) : // Sector count is a multiple of 63 + (img_size_sect / spt) + 1; // There's a remainder, so we add one track - const decltype(img_size_sect) disksize {disk_tracks * spt * SECT_SIZE}; + const decltype(img_size_sect) disksize {disk_tracks * spt * SECT_SIZE}; */ const auto disksize = (img_size_sect + extra_sectors) * SECT_SIZE; @@ -126,9 +127,9 @@ int main(int argc, char** argv) { } cout << "Creating disk of size: " - //<< "Cyls: " << cylinders << "\n" - //<< "Heads: " << heads << "\n" - //<< "Sec/Tr: " << spt << "\n" + //<< "Cyls: " << cylinders << "\n" + //<< "Heads: " << heads << "\n" + //<< "Sec/Tr: " << spt << "\n" << "=> " << (disksize / SECT_SIZE) << " sectors\n" << "=> " << disksize << " bytes\n"; @@ -153,7 +154,7 @@ int main(int argc, char** argv) { cout << "Signature: "; for(int i {0}; i < EI_NIDENT; ++i) { - cout << elf_header->e_ident[i]; + cout << elf_header->e_ident[i]; } cout << "\nType: " << ((elf_header->e_type == ET_EXEC) ? " ELF Executable\n" : "Non-executable\n");