diff --git a/kernel/Makefile b/kernel/Makefile
index a1b142c2..901dbbe7 100644
--- a/kernel/Makefile
+++ b/kernel/Makefile
@@ -6,7 +6,6 @@ include ../build-aux/dirs.mak
# Default values in case the user doesn't override these variables.
OPTLEVEL?=$(DEFAULT_OPTLEVEL)
-DISKWRITE?=1
CPPFLAGS?=
CXXFLAGS?=$(OPTLEVEL)
@@ -19,7 +18,6 @@ CXXFLAGS:=$(CXXFLAGS) -Wall -Wextra -ffreestanding -fbuiltin -std=gnu++11 \
ifeq ($(PANIC_SHORT),1)
CPPFLAGS:=$(CPPFLAGS) -DPANIC_SHORT
endif
-CPPFLAGS:=$(CPPFLAGS) -DENABLE_DISKWRITE=$(DISKWRITE)
ifdef VERSION
CPPFLAGS:=$(CPPFLAGS) -DVERSIONSTR=\"$(VERSION)\"
endif
@@ -81,7 +79,6 @@ OBJS=\
$(CPUOBJS) \
addralloc.o \
alarm.o \
-ata.o \
clock.o \
com.o \
copy.o \
@@ -89,6 +86,10 @@ $(CPUDIR)/kthread.o \
crc32.o \
debugger.o \
descriptor.o \
+disk/ata/ata.o \
+disk/ata/hba.o \
+disk/ata/port.o \
+disk/node.o \
dtable.o \
elf.o \
fcache.o \
diff --git a/kernel/ata.cpp b/kernel/ata.cpp
deleted file mode 100644
index efa079f2..00000000
--- a/kernel/ata.cpp
+++ /dev/null
@@ -1,477 +0,0 @@
-/*******************************************************************************
-
- Copyright(C) Jonas 'Sortie' Termansen 2011, 2012, 2013.
-
- This file is part of Sortix.
-
- Sortix is free software: you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation, either version 3 of the License, or (at your option) any later
- version.
-
- Sortix is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
- FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
- details.
-
- You should have received a copy of the GNU General Public License along with
- Sortix. If not, see .
-
- ata.cpp
- Allowes access to block devices over ATA PIO.
-
-*******************************************************************************/
-
-#include
-#include
-#include
-
-#include
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "ata.h"
-
-// TODO: Use the PCI to detect ATA devices instead of relying on them being on
-// standard locations.
-
-namespace Sortix {
-
-const uint16_t PRIMARY_BUS_OFFSET = 0x1F0;
-const uint16_t SECONDARY_BUS_OFFSET = 0x170;
-const uint16_t DATA = 0x0;
-const uint16_t FEATURE = 0x1;
-const uint16_t ERROR = 0x1;
-const uint16_t SECTOR_COUNT = 0x2;
-const uint16_t LBA_LOW = 0x3;
-const uint16_t LBA_MID = 0x4;
-const uint16_t LBA_HIGH = 0x5;
-const uint16_t DRIVE_SELECT = 0x6;
-const uint16_t COMMAND = 0x7;
-const uint16_t STATUS = 0x7;
-const uint8_t CMD_READ = 0x20;
-const uint8_t CMD_READ_EXT = 0x24;
-const uint8_t CMD_WRITE = 0x30;
-const uint8_t CMD_WRITE_EXT = 0x34;
-const uint8_t CMD_FLUSH_CACHE = 0xE7;
-const uint8_t CMD_IDENTIFY = 0xEC;
-const uint8_t STATUS_ERROR = (1<<0);
-const uint8_t STATUS_DATAREADY = (1<<3);
-const uint8_t STATUS_DRIVEFAULT = (1<<5);
-const uint8_t STATUS_BUSY = (1<<7);
-const uint8_t CTL_NO_INTERRUPT = (1<<1);
-const uint8_t CTL_RESET = (1<<2);
-
-namespace ATA {
-
-class ATANode : public AbstractInode
-{
-public:
- ATANode(ATADrive* drive, uid_t owner, gid_t group, mode_t mode, dev_t dev,
- ino_t ino);
- virtual ~ATANode();
- virtual int sync(ioctx_t* ctx);
- virtual int truncate(ioctx_t* ctx, off_t length);
- virtual off_t lseek(ioctx_t* ctx, off_t offset, int whence);
- virtual ssize_t pread(ioctx_t* ctx, uint8_t* buf, size_t count,
- off_t off);
- virtual ssize_t pwrite(ioctx_t* ctx, const uint8_t* buf, size_t count,
- off_t off);
-
-private:
- kthread_mutex_t filelock;
- ATADrive* drive;
-
-};
-
-ATANode::ATANode(ATADrive* drive, uid_t owner, gid_t group, mode_t mode,
- dev_t dev, ino_t /*ino*/)
-{
- inode_type = INODE_TYPE_FILE;
- filelock = KTHREAD_MUTEX_INITIALIZER;
- this->dev = dev;
- this->ino = (ino_t) this;
- this->stat_uid = owner;
- this->stat_gid = group;
- this->type = S_IFBLK;
- this->stat_size = (off_t) drive->GetSize();
- this->stat_mode = (mode & S_SETABLE) | this->type;
- this->stat_blksize = (blksize_t) drive->GetSectorSize();
- this->stat_blocks = (blkcnt_t) drive->GetNumSectors();
- this->drive = drive;
-}
-
-ATANode::~ATANode()
-{
- delete drive;
-}
-
-int ATANode::sync(ioctx_t* /*ctx*/)
-{
- // TODO: Actually sync the device here!
- return 0;
-}
-
-int ATANode::truncate(ioctx_t* /*ctx*/, off_t length)
-{
- ScopedLock lock(&filelock);
- if ( length == drive->GetSize() ) { return 0; }
- errno = EPERM;
- return -1;
-}
-
-off_t ATANode::lseek(ioctx_t* /*ctx*/, off_t offset, int whence)
-{
- ScopedLock lock(&filelock);
- if ( whence == SEEK_SET )
- return offset;
- if ( whence == SEEK_END )
- return (off_t) drive->GetSize() + offset;
- errno = EINVAL;
- return -1;
-}
-
-ssize_t ATANode::pread(ioctx_t* /*ctx*/, uint8_t* buf, size_t count, off_t off)
-{
- // TODO: SECURITY: Use ioctx copy functions to copy or we have a serious
- // security hole if invoked from user-space!
- size_t numbytes = drive->Read(off, buf, count);
- if ( numbytes < count )
- return -1;
- return (ssize_t) numbytes;
-}
-
-ssize_t ATANode::pwrite(ioctx_t* /*ctx*/, const uint8_t* buf, size_t count,
- off_t off)
-{
- // TODO: SECURITY: Use ioctx copy functions to copy or we have a serious
- // security hole if invoked from user-space!
- size_t numbytes = drive->Write(off, buf, count);
- if ( numbytes < count )
- return -1;
- return (ssize_t) numbytes;
-}
-
-void DetectDrive(const char* devpath, Ref slashdev, unsigned busid,
- ATABus* bus, unsigned driveid)
-{
- unsigned ataid = busid*2 + driveid;
- ATADrive* drive = bus->Instatiate(driveid);
- if ( !drive )
- return;
- Ref node(new ATANode(drive, 0, 0, 0660, slashdev->dev, 0));
- if ( !node )
- Panic("Unable to allocate memory for ATA drive inode.");
- const size_t NAMELEN = 64;
- char name[NAMELEN];
- snprintf(name, NAMELEN, "ata%u", ataid);
- ioctx_t ctx; SetupKernelIOCtx(&ctx);
- if ( LinkInodeInDir(&ctx, slashdev, name, node) != 0 )
- PanicF("Unable to link %s/%s to ATA driver inode.", devpath, name);
-}
-
-void DetectBus(const char* devpath, Ref slashdev, unsigned busid,
- uint16_t ioport, uint16_t altio)
-{
- ATABus* bus = ATA::CreateBus(ioport, altio);
- if ( !bus )
- return;
- DetectDrive(devpath, slashdev, busid, bus, 0);
- DetectDrive(devpath, slashdev, busid, bus, 1);
-}
-
-void Init(const char* devpath, Ref slashdev)
-{
- DetectBus(devpath, slashdev, 0, 0x1F0, 0x3F6);
- DetectBus(devpath, slashdev, 1, 0x170, 0x366);
-}
-
-ATABus* CreateBus(uint16_t portoffset, uint16_t altport)
-{
- unsigned status = inport8(portoffset + STATUS);
- // Detect if there is no such bus.
- if ( status == 0xFF )
- {
- errno = ENODEV;
- return NULL;
- }
- return new ATABus(portoffset, altport);
-}
-
-} // namespace ATA
-
-void Wait400NSecs(uint16_t iobase)
-{
- // Now wait 400 ns for the drive to be ready.
- for ( unsigned i = 0; i < 4; i++ ) { inport8(iobase + STATUS); }
-}
-
-ATABus::ATABus(uint16_t portoffset, uint16_t altport)
-{
- this->iobase = portoffset;
- this->altport = altport;
- this->curdriveid = 0;
-}
-
-ATABus::~ATABus()
-{
-}
-
-ATADrive* ATABus::Instatiate(unsigned driveid)
-{
- if ( 1 < driveid )
- return errno = EINVAL, (ATADrive*) NULL;
- curdriveid = 0;
-
- uint8_t drivemagic = 0xA0 | (driveid << 4);
- outport8(iobase + DRIVE_SELECT, drivemagic);
- outport8(iobase + SECTOR_COUNT, 0);
- outport8(iobase + LBA_LOW, 0);
- outport8(iobase + LBA_MID, 0);
- outport8(iobase + LBA_HIGH, 0);
- outport8(iobase + COMMAND, CMD_IDENTIFY);
- uint8_t status;
- while ( true )
- {
- status = inport8(iobase + STATUS);
- if ( !status || status == 0xFF )
- return errno = ENODEV, (ATADrive*) NULL;
- if ( !(status & STATUS_BUSY) )
- break;
- }
- // Check for ATAPI device not following spec.
- if ( inport8(iobase + LBA_MID) || inport8(iobase + LBA_MID) )
- return errno = ENODEV, (ATADrive*) NULL;
- while ( (status & STATUS_BUSY) || (!(status & STATUS_DATAREADY) && !(status & STATUS_ERROR)) )
- status = inport8(iobase + STATUS);
- if ( status & STATUS_ERROR )
- {
- unsigned mid = inport8(iobase + LBA_MID);
- unsigned high = inport8(iobase + LBA_HIGH);
- if ( mid == 0x14 && high == 0xEB )
- {
- //Log::PrintF("Found ATAPI device instead of ATA\n");
- }
- else if ( mid == 0x3C && high == 0xC3 )
- {
- //Log::PrintF("Found SATA device instead of ATA\n");
- }
- else if ( mid || high )
- {
- //Log::PrintF("Found unknown device instead of ATA\n");
- }
- else
- {
- //Log::PrintF("Error status during identify\n");
- }
- return errno = EIO, (ATADrive*) NULL;
- }
- ATADrive* drive = new ATADrive(this, driveid, iobase, altport);
- return drive;
-}
-
-bool ATABus::SelectDrive(unsigned driveid)
-{
- if ( driveid == curdriveid ) { return true; }
- if ( 1 < driveid ) { errno = EINVAL; return false; }
-
- uint8_t drivemagic = 0xA0 | (driveid << 4);
- outport8(iobase + DRIVE_SELECT, drivemagic);
- Wait400NSecs(iobase);
- return true;
-}
-
-const size_t META_LBA28 = 60;
-const size_t META_FLAGS = 83;
-const size_t META_LBA48 = 100;
-const uint16_t FLAG_LBA48 = 1 << 10;
-
-ATADrive::ATADrive(ATABus* bus, unsigned driveid, uint16_t portoffset, uint16_t altport)
-{
- this->atalock = KTHREAD_MUTEX_INITIALIZER;
- this->bus = bus;
- this->driveid = driveid;
- this->iobase = portoffset;
- this->altport = altport;
- for ( size_t i = 0; i < 256; i++ )
- meta[i] = inport16(iobase + DATA);
- lba48 = meta[META_FLAGS] & FLAG_LBA48;
- numsectors = 0;
- if ( lba48 )
- {
- numsectors = (uint64_t) meta[META_LBA48 + 0] << 0
- | (uint64_t) meta[META_LBA48 + 1] << 16
- | (uint64_t) meta[META_LBA48 + 2] << 32
- | (uint64_t) meta[META_LBA48 + 3] << 48;
- }
- else
- {
- numsectors = meta[META_LBA28 + 0] << 0
- | meta[META_LBA28 + 1] << 16;
- }
- sectorsize = 512; // TODO: Detect this!
- Initialize();
-}
-
-ATADrive::~ATADrive()
-{
-}
-
-off_t ATADrive::GetSectorSize()
-{
- return sectorsize;
-}
-
-off_t ATADrive::GetNumSectors()
-{
- return numsectors;
-}
-
-bool ATADrive::PrepareIO(bool write, off_t sector)
-{
- if ( numsectors <= sector ) { errno = EINVAL; return false; }
- if ( write && !ENABLE_DISKWRITE )
- {
- errno = EPERM;
- return false;
- }
- bus->SelectDrive(driveid);
- uint8_t mode = (lba48) ? 0x40 : 0xE0;
- mode |= driveid << 4;
- mode |= (lba48) ? 0 : (sector >> 24) & 0x0F;
- outport8(iobase + DRIVE_SELECT, mode);
- uint16_t sectorcount = 1;
- uint8_t sectorcountlow = sectorcount & 0xFF;
- uint8_t sectorcounthigh = (sectorcount >> 8) & 0xFF;
- if ( lba48 )
- {
- outport8(iobase + SECTOR_COUNT, sectorcounthigh);
- outport8(iobase + LBA_LOW, (sector >> 24) & 0xFF);
- outport8(iobase + LBA_MID, (sector >> 32) & 0xFF);
- outport8(iobase + LBA_HIGH, (sector >> 40) & 0xFF);
- }
- outport8(iobase + SECTOR_COUNT, sectorcountlow);
- outport8(iobase + LBA_LOW, sector & 0xFF);
- outport8(iobase + LBA_MID, (sector >> 8) & 0xFF);
- outport8(iobase + LBA_HIGH, (sector >> 16) & 0xFF);
- uint8_t command = (write) ? CMD_WRITE : CMD_READ;
- if ( lba48 ) { command = (write) ? CMD_WRITE_EXT : CMD_READ_EXT; }
- outport8(iobase + COMMAND, command);
- while ( true )
- {
- uint8_t status = inport8(iobase + STATUS);
- if ( status & STATUS_BUSY ) { continue; }
- if ( status & STATUS_DATAREADY ) { break; }
- if ( status & STATUS_ERROR ) { errno = EIO; return false; }
- if ( status & STATUS_DRIVEFAULT ) { errno = EIO; return false; }
- }
- return true;
-}
-
-bool ATADrive::ReadSector(off_t sector, uint8_t* dest)
-{
- ScopedLock lock(&atalock);
- if ( !PrepareIO(false, sector) ) { return false; }
- uint16_t* destword = (uint16_t*) dest;
- for ( size_t i = 0; i < sectorsize/2; i++ )
- {
- destword[i] = inport16(iobase + DATA);
- }
- Wait400NSecs(iobase);
- inport8(iobase + STATUS);
- return true;
-}
-
-bool ATADrive::WriteSector(off_t sector, const uint8_t* src)
-{
- ScopedLock lock(&atalock);
- if ( !PrepareIO(true, sector) ) { return false; }
- const uint16_t* srcword = (const uint16_t*) src;
- for ( size_t i = 0; i < sectorsize/2; i++ )
- {
- outport16(iobase + DATA, srcword[i]);
- }
- Wait400NSecs(iobase);
- outport8(iobase + COMMAND, CMD_FLUSH_CACHE);
- while ( true )
- {
- uint8_t status = inport8(iobase + STATUS);
- if ( status & STATUS_ERROR ) { errno = EIO; return false; }
- if ( status & STATUS_DRIVEFAULT ) { errno = EIO; return false; }
- if ( !(status & STATUS_BUSY) ) { break; }
- }
- return true;
-}
-
-size_t ATADrive::Read(off_t byteoffset, uint8_t* dest, size_t numbytes)
-{
- size_t sofar = 0;
- size_t leadingbytes = byteoffset % sectorsize;
- if ( leadingbytes || numbytes < sectorsize )
- {
- size_t wanted = sectorsize - leadingbytes;
- if ( numbytes < wanted ) { wanted = numbytes; }
- uint8_t temp[512 /*sectorsize*/];
- if ( !ReadSector(byteoffset/sectorsize, temp) ) { return sofar; }
- memcpy(dest + sofar, temp + leadingbytes, wanted);
- sofar += wanted;
- numbytes -= wanted;
- byteoffset += wanted;
- }
-
- while ( sectorsize <= numbytes )
- {
- if ( !ReadSector(byteoffset/sectorsize, dest + sofar) ) { return sofar; }
- sofar += sectorsize;
- numbytes -= sectorsize;
- byteoffset += sectorsize;
- }
-
- if ( numbytes ) { return sofar + Read(byteoffset, dest + sofar, numbytes); }
- return sofar;
-}
-
-size_t ATADrive::Write(off_t byteoffset, const uint8_t* src, size_t numbytes)
-{
- size_t sofar = 0;
- size_t leadingbytes = byteoffset % sectorsize;
- if ( leadingbytes || numbytes < sectorsize )
- {
- size_t wanted = sectorsize - leadingbytes;
- if ( numbytes < wanted ) { wanted = numbytes; }
- uint8_t temp[512 /*sectorsize*/];
- if ( !ReadSector(byteoffset/sectorsize, temp) ) { return sofar; }
- memcpy(temp + leadingbytes, src + sofar, wanted);
- if ( !WriteSector(byteoffset/sectorsize, temp) ) { return sofar; }
- sofar += wanted;
- numbytes -= wanted;
- byteoffset += wanted;
- }
-
- while ( sectorsize <= numbytes )
- {
- if ( !WriteSector(byteoffset/sectorsize, src + sofar) ) { return sofar; }
- sofar += sectorsize;
- numbytes -= sectorsize;
- byteoffset += sectorsize;
- }
-
- if ( numbytes ) { return sofar + Write(byteoffset, src + sofar, numbytes); }
- return sofar;
-}
-
-void ATADrive::Initialize()
-{
- bus->SelectDrive(driveid);
- outport8(iobase + COMMAND, CTL_NO_INTERRUPT);
-}
-
-} // namespace Sortix
diff --git a/kernel/ata.h b/kernel/ata.h
deleted file mode 100644
index 2363030e..00000000
--- a/kernel/ata.h
+++ /dev/null
@@ -1,95 +0,0 @@
-/*******************************************************************************
-
- Copyright(C) Jonas 'Sortie' Termansen 2011, 2012.
-
- This file is part of Sortix.
-
- Sortix is free software: you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation, either version 3 of the License, or (at your option) any later
- version.
-
- Sortix is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
- FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
- details.
-
- You should have received a copy of the GNU General Public License along with
- Sortix. If not, see .
-
- ata.h
- Allowes access to block devices over ATA PIO.
-
-*******************************************************************************/
-
-#ifndef SORTIX_ATA_H
-#define SORTIX_ATA_H
-
-#include
-#include
-
-namespace Sortix {
-
-class Descriptor;
-class ATABus;
-class ATADrive;
-
-class ATABus
-{
-public:
- ATABus(uint16_t portoffset, uint16_t altport);
- ~ATABus();
-
-public:
- ATADrive* Instatiate(unsigned driveid);
- bool SelectDrive(unsigned driveid);
-
-private:
- unsigned curdriveid;
- uint16_t iobase;
- uint16_t altport;
-
-};
-
-class ATADrive
-{
-public:
- off_t GetSectorSize();
- off_t GetNumSectors();
- off_t GetSize() { return GetSectorSize() * GetNumSectors(); }
- bool ReadSector(off_t sector, uint8_t* dest);
- bool WriteSector(off_t sector, const uint8_t* src);
- size_t Read(off_t byteoffset, uint8_t* dest, size_t numbytes);
- size_t Write(off_t byteoffset, const uint8_t* src, size_t numbytes);
-
-public:
- ATADrive(ATABus* bus, unsigned driveid, uint16_t portoffset, uint16_t altport);
- ~ATADrive();
-
-private:
- void Initialize();
- bool PrepareIO(bool write, off_t sector);
-
-private:
- kthread_mutex_t atalock;
- unsigned driveid;
- uint16_t meta[256];
- uint16_t iobase;
- uint16_t altport;
- ATABus* bus;
- bool lba48;
- size_t sectorsize;
- off_t numsectors;
-
-};
-
-namespace ATA {
-
-void Init(const char* devpath, Ref slashdev);
-ATABus* CreateBus(uint16_t portoffset, uint16_t altport);
-
-} // namespace ATA
-
-} // namespace Sortix
-
-#endif
diff --git a/kernel/disk/ata/ata.cpp b/kernel/disk/ata/ata.cpp
new file mode 100644
index 00000000..79905eb8
--- /dev/null
+++ b/kernel/disk/ata/ata.cpp
@@ -0,0 +1,66 @@
+/*******************************************************************************
+
+ Copyright(C) Jonas 'Sortie' Termansen 2011, 2012, 2013, 2014, 2015, 2016.
+
+ This file is part of Sortix.
+
+ Sortix is free software: you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation, either version 3 of the License, or (at your option) any later
+ version.
+
+ Sortix is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ details.
+
+ You should have received a copy of the GNU General Public License along with
+ Sortix. If not, see .
+
+ disk/ata/ata.cpp
+ Driver for ATA.
+
+*******************************************************************************/
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+#include "ata.h"
+#include "hba.h"
+
+namespace Sortix {
+namespace ATA {
+
+static void InitializeDevice(Ref dev, const char* devpath,
+ uint32_t devaddr)
+{
+ HBA* hba = new HBA(devaddr);
+ if ( !hba )
+ PanicF("Failed to allocate ATA driver for PCI device 0x%X", devaddr);
+
+ if ( !hba->Initialize(dev, devpath) )
+ return delete hba;
+}
+
+
+void Init(const char* devpath, Ref dev)
+{
+ uint32_t devaddr;
+ pcifind_t filter;
+
+ memset(&filter, 255, sizeof(filter));
+ filter.classid = 0x01;
+ filter.subclassid = 0x01;
+ devaddr = 0;
+ while ( (devaddr = PCI::SearchForDevices(filter, devaddr)) )
+ InitializeDevice(dev, devpath, devaddr);
+}
+
+} // namespace ATA
+} // namespace Sortix
diff --git a/kernel/disk/ata/ata.h b/kernel/disk/ata/ata.h
new file mode 100644
index 00000000..95044641
--- /dev/null
+++ b/kernel/disk/ata/ata.h
@@ -0,0 +1,39 @@
+/*******************************************************************************
+
+ Copyright(C) Jonas 'Sortie' Termansen 2011, 2012, 2015.
+
+ This file is part of Sortix.
+
+ Sortix is free software: you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation, either version 3 of the License, or (at your option) any later
+ version.
+
+ Sortix is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ details.
+
+ You should have received a copy of the GNU General Public License along with
+ Sortix. If not, see .
+
+ disk/ata/ata.h
+ Driver for ATA.
+
+*******************************************************************************/
+
+#ifndef SORTIX_DISK_ATA_ATA_H
+#define SORTIX_DISK_ATA_ATA_H
+
+#include
+#include
+
+namespace Sortix {
+namespace ATA {
+
+void Init(const char* devpath, Ref dev);
+
+} // namespace ATA
+} // namespace Sortix
+
+#endif
diff --git a/kernel/disk/ata/hba.cpp b/kernel/disk/ata/hba.cpp
new file mode 100644
index 00000000..fc39a78c
--- /dev/null
+++ b/kernel/disk/ata/hba.cpp
@@ -0,0 +1,337 @@
+/*******************************************************************************
+
+ Copyright(C) Jonas 'Sortie' Termansen 2011, 2012, 2013, 2014, 2015, 2016.
+
+ This file is part of Sortix.
+
+ Sortix is free software: you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation, either version 3 of the License, or (at your option) any later
+ version.
+
+ Sortix is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ details.
+
+ You should have received a copy of the GNU General Public License along with
+ Sortix. If not, see .
+
+ disk/ata/hba.cpp
+ Driver for ATA.
+
+*******************************************************************************/
+
+#include
+#include
+#include
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "../node.h"
+
+#include "hba.h"
+#include "port.h"
+#include "registers.h"
+
+namespace Sortix {
+namespace ATA {
+
+static unsigned long AllocateDiskNumber()
+{
+ static unsigned long next_disk_number = 0;
+ return InterlockedIncrement(&next_disk_number).o;
+}
+
+static void sleep_400_nanoseconds()
+{
+ struct timespec delay = timespec_make(0, 400);
+ Clock* clock = Time::GetClock(CLOCK_BOOT);
+ clock->SleepDelay(delay);
+}
+
+Channel::Channel()
+{
+ hw_lock = KTHREAD_MUTEX_INITIALIZER;
+ interrupt_registered = false;
+ drives[0] = NULL;
+ drives[1] = NULL;
+}
+
+Channel::~Channel()
+{
+ if ( interrupt_registered )
+ Interrupt::UnregisterHandler(interrupt_index, &interrupt_registration);
+ // TODO: Destroy all the ports?
+}
+
+void Channel::LogF(const char* format, ...)
+{
+ // TODO: Print this line in an atomic manner.
+ const char* cdesc = channel_index == 0 ? "primary" : "secondary";
+ Log::PrintF("ata: pci 0x%X: %s channel: ", devaddr, cdesc);
+ va_list ap;
+ va_start(ap, format);
+ Log::PrintFV(format, ap);
+ va_end(ap);
+ Log::PrintF("\n");
+}
+
+void Channel::SelectDrive(unsigned int drive_index) // hw_lock locked
+{
+ if ( current_drive == drive_index )
+ return;
+
+#if 0
+ // TODO: Perhaps not do this here. This appears to time out on boot on real
+ // hardware where there is no drive there.
+ if ( !wait_inport8_clear(port_base + REG_STATUS,
+ STATUS_BUSY | STATUS_DATAREADY, false, 10000 /*ms*/) )
+ {
+ LogF("error: timed out waiting for idle");
+ // TODO: This can fail!
+ //return errno = EIO, false;
+ return;
+ }
+#endif
+
+ uint8_t value = 0xA0 | (drive_index << 4);
+ outport8(port_base + REG_DRIVE_SELECT, value);
+ //outport8(port_control, value); // TODO: Or is it port_control we use?
+
+ sleep_400_nanoseconds();
+
+ // TODO: Do we need to wait for non-busy now? Can this operation fail?
+
+ current_drive = drive_index;
+}
+
+void Channel::OnInterrupt()
+{
+ // Check whether the interrupt came from this channel.
+ uint8_t status = inport8(busmaster_base + BUSMASTER_REG_STATUS);
+ if ( !(status & BUSMASTER_STATUS_INTERRUPT_PENDING) )
+ return;
+
+ if ( status & BUSMASTER_STATUS_DMA_FAILURE )
+ {
+ // TODO: What do we do here?
+ }
+
+ // Clear interrupt flag.
+ // TODO: This filters away BUSMASTER_STATUS_DMA and
+ // BUSMASTER_STATUS_DMA_FAILURE and might be a bug?
+ //status &= 0xF8 | BUSMASTER_STATUS_DMA;
+ status |= BUSMASTER_STATUS_INTERRUPT_PENDING;
+ outport8(busmaster_base + BUSMASTER_REG_STATUS, status);
+
+ if ( current_drive < 2 && drives[current_drive] )
+ drives[current_drive]->OnInterrupt();
+}
+
+void Channel__OnInterrupt(struct interrupt_context*, void* context)
+{
+ ((Channel*) context)->OnInterrupt();
+}
+
+static
+void FixDefaultDeviceBars(pcibar_t* basebar, pcibar_t* ctrlbar, uint8_t* irq,
+ unsigned int channel_index, uint8_t interface)
+{
+ bool compatibility = interface == 0x00 || interface == 0x02;
+
+ if ( compatibility )
+ *irq = channel_index == 0 ? 14 : 15;
+
+ if ( compatibility ||
+ basebar->addr_raw == 0 ||
+ (basebar->is_iospace() && !ctrlbar->ioaddr()) )
+ {
+ uint16_t ioport = channel_index == 0 ? 0x1F0 : 0x170;
+ basebar->addr_raw = ioport | PCIBAR_TYPE_IOSPACE;
+ basebar->size_raw = 8;
+ }
+
+ if ( compatibility ||
+ ctrlbar->addr_raw == 0 ||
+ (ctrlbar->is_iospace() && !ctrlbar->ioaddr()) )
+ {
+ uint16_t ioport = channel_index == 0 ? 0x3F4 : 0x374;
+ ctrlbar->addr_raw = ioport | PCIBAR_TYPE_IOSPACE;
+ ctrlbar->size_raw = 4; // TODO: This is just a guess.
+ }
+}
+
+bool Channel::Initialize(Ref dev, const char* devpath)
+{
+ uint8_t prog_if = PCI::Read8(devaddr, PCIFIELD_PROG_IF);
+ uint8_t interface = (prog_if >> (channel_index * 2)) & 0x3;
+ pcibar_t basebar = PCI::GetBAR(devaddr, 2 * channel_index + 0);
+ pcibar_t ctrlbar = PCI::GetBAR(devaddr, 2 * channel_index + 1);
+ pcibar_t busmasterbar = PCI::GetBAR(devaddr, 4);
+ uint8_t irq = PCI::Read8(devaddr, PCIFIELD_INTERRUPT_LINE);
+ FixDefaultDeviceBars(&basebar, &ctrlbar, &irq, channel_index, interface);
+
+ if ( !basebar.is_iospace() )
+ {
+ LogF("ignoring: non-iospace base BAR");
+ return errno = EINVAL, false;
+ }
+
+ if ( basebar.size() < 8 )
+ {
+ LogF("ignoring: too small base BAR");
+ return errno = EINVAL, false;
+ }
+
+ if ( !ctrlbar.is_iospace() )
+ {
+ LogF("ignoring: non-iospace control BAR");
+ return errno = EINVAL, false;
+ }
+
+ if ( ctrlbar.size() < 4 )
+ {
+ LogF("ignoring: too small control BAR");
+ return errno = EINVAL, false;
+ }
+
+ if ( !busmasterbar.is_iospace() )
+ {
+ LogF("ignoring: non-iospace bus master BAR");
+ return errno = EINVAL, false;
+ }
+
+ if ( busmasterbar.size() < 16 )
+ {
+ LogF("ignoring: too small bus master BAR");
+ return errno = EINVAL, false;
+ }
+
+ port_base = basebar.addr();
+ if ( inport8(port_base + REG_STATUS) == 0xFF )
+ return errno = ENODEV, false; // Non-existent.
+
+ // TODO: Ensure this is the correct logic.
+ port_control = ctrlbar.addr() + 2;
+
+ busmaster_base = busmasterbar.addr() + 8 * channel_index;
+
+ current_drive = (unsigned int) -1; // We don't know.
+
+ for ( unsigned int i = 0; i < 2; i++ )
+ {
+ drives[i] = NULL;
+
+ ScopedLock lock(&hw_lock);
+
+ SelectDrive(i);
+
+ // TODO: May we do this before sending an IDENTITY command?
+ uint8_t status = inport8(port_base + REG_STATUS);
+ if ( status == 0 )
+ continue; // Non-existent.
+
+ const char* name = i == 0 ? "master" : "slave";
+
+ drives[i] = new Port(this, i);
+ if ( !drives[i] )
+ {
+ LogF("error: failed to allocate %s drive", name);
+ continue;
+ }
+
+ if ( !drives[i]->Initialize() )
+ {
+ delete drives[i];
+ drives[i] = NULL;
+ continue;
+ }
+ }
+
+ interrupt_index = Interrupt::IRQ0 + irq;
+
+ interrupt_registration.handler = Channel__OnInterrupt;
+ interrupt_registration.context = this;
+ Interrupt::RegisterHandler(interrupt_index, &interrupt_registration);
+ interrupt_registered = true;
+
+ for ( unsigned int i = 0; i < 2; i++ )
+ {
+ if ( !drives[i] )
+ continue;
+ if ( !drives[i]->FinishInitialize() )
+ {
+ // TODO: Gracefully destroy the drive here?
+ // TODO: Unsafe with respect to interrupt handler.
+ delete drives[i];
+ drives[i] = NULL;
+ continue;
+ }
+ }
+
+ for ( unsigned int i = 0; i < 2; i++ )
+ {
+ if ( !drives[i] )
+ continue;
+ unsigned long number = AllocateDiskNumber();
+ char name[3 + sizeof(unsigned long) * 3];
+ snprintf(name, sizeof(name), "ata%lu", number);
+ Ref node(new PortNode(drives[i], 0, 0, 0660, dev->dev, 0));
+ if ( !node )
+ PanicF("Unable to allocate memory for %s/%s inode", devpath, name);
+ ioctx_t ctx; SetupKernelIOCtx(&ctx);
+ if ( LinkInodeInDir(&ctx, dev, name, node) != 0 )
+ PanicF("Unable to link %s/%s to ATA driver inode", devpath, name);
+ }
+
+ return true;
+}
+
+HBA::HBA(uint32_t devaddr)
+{
+ this->devaddr = devaddr;
+}
+
+HBA::~HBA()
+{
+}
+
+bool HBA::InitializeChannel(Ref dev, const char* devpath,
+ unsigned int channel_index)
+{
+ channels[channel_index].devaddr = devaddr;
+ channels[channel_index].hba = this;
+ channels[channel_index].channel_index = channel_index;
+ return channels[channel_index].Initialize(dev, devpath);
+}
+
+bool HBA::Initialize(Ref dev, const char* devpath)
+{
+ uint16_t dev_pci_command_cur = PCI::Read16(devaddr, PCIFIELD_COMMAND);
+ uint16_t dev_pci_command_new = dev_pci_command_cur;
+ dev_pci_command_new &= ~PCIFIELD_COMMAND_INTERRUPT_DISABLE;
+ dev_pci_command_new |= PCIFIELD_COMMAND_IO_SPACE;
+ dev_pci_command_new |= PCIFIELD_COMMAND_BUS_MASTER;
+ if ( dev_pci_command_cur != dev_pci_command_new )
+ PCI::Write16(devaddr, PCIFIELD_COMMAND, dev_pci_command_new);
+
+ InitializeChannel(dev, devpath, 0);
+ InitializeChannel(dev, devpath, 1);
+
+ return true;
+}
+
+} // namespace ATA
+} // namespace Sortix
diff --git a/kernel/disk/ata/hba.h b/kernel/disk/ata/hba.h
new file mode 100644
index 00000000..42f32426
--- /dev/null
+++ b/kernel/disk/ata/hba.h
@@ -0,0 +1,98 @@
+/*******************************************************************************
+
+ Copyright(C) Jonas 'Sortie' Termansen 2011, 2012, 2013, 2014, 2015, 2016.
+
+ This file is part of Sortix.
+
+ Sortix is free software: you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation, either version 3 of the License, or (at your option) any later
+ version.
+
+ Sortix is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ details.
+
+ You should have received a copy of the GNU General Public License along with
+ Sortix. If not, see .
+
+ disk/ata/hba.h
+ Driver for ATA.
+
+*******************************************************************************/
+
+#ifndef SORTIX_DISK_ATA_HBA_H
+#define SORTIX_DISK_ATA_HBA_H
+
+#include
+
+#include
+#include
+#include
+
+namespace Sortix {
+namespace ATA {
+
+class HBA;
+class Port;
+
+class Channel
+{
+ friend class Port;
+ friend class HBA;
+
+public:
+ Channel();
+ ~Channel();
+
+public:
+ bool Initialize(Ref dev, const char* devpath);
+ void OnInterrupt();
+
+private:
+ __attribute__((format(printf, 2, 3)))
+ void LogF(const char* format, ...);
+ void SelectDrive(unsigned int drive_index);
+
+private:
+ struct interrupt_handler interrupt_registration;
+ kthread_mutex_t hw_lock;
+ uint32_t devaddr;
+ HBA* hba;
+ unsigned int channel_index;
+ unsigned int current_drive;
+ uint16_t port_base;
+ uint16_t port_control;
+ uint16_t busmaster_base;
+ Port* drives[2];
+ unsigned int interrupt_index;
+ bool interrupt_registered;
+
+};
+
+class HBA
+{
+ friend class Port;
+
+public:
+ HBA(uint32_t devaddr);
+ ~HBA();
+
+public:
+ bool Initialize(Ref dev, const char* devpath);
+
+private:
+ bool InitializeChannel(Ref dev, const char* devpath,
+ unsigned int channel_index);
+
+private:
+ uint32_t devaddr;
+ Channel channels[2];
+
+};
+
+} // namespace ATA
+} // namespace Sortix
+
+#endif
diff --git a/kernel/disk/ata/port.cpp b/kernel/disk/ata/port.cpp
new file mode 100644
index 00000000..5379fa1a
--- /dev/null
+++ b/kernel/disk/ata/port.cpp
@@ -0,0 +1,784 @@
+/*******************************************************************************
+
+ Copyright(C) Jonas 'Sortie' Termansen 2011, 2012, 2013, 2014, 2015, 2016.
+
+ This file is part of Sortix.
+
+ Sortix is free software: you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation, either version 3 of the License, or (at your option) any later
+ version.
+
+ Sortix is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ details.
+
+ You should have received a copy of the GNU General Public License along with
+ Sortix. If not, see .
+
+ disk/ata/port.cpp
+ Driver for ATA.
+
+*******************************************************************************/
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "hba.h"
+#include "port.h"
+#include "registers.h"
+
+namespace Sortix {
+namespace ATA {
+
+static void copy_ata_string(char* dest, const char* src, size_t length)
+{
+ for ( size_t i = 0; i < length; i += 2 )
+ {
+ dest[i + 0] = src[i + 1];
+ dest[i + 1] = src[i + 0];
+ }
+ length = strnlen(dest, length);
+ while ( 0 < length && dest[length - 1] == ' ' )
+ length--;
+ dest[length] = '\0';
+}
+
+static void sleep_400_nanoseconds()
+{
+ struct timespec delay = timespec_make(0, 400);
+ Clock* clock = Time::GetClock(CLOCK_BOOT);
+ clock->SleepDelay(delay);
+}
+
+Port::Port(Channel* channel, unsigned int port_index)
+{
+ this->channel = channel;
+ this->port_index = port_index;
+ memset(&control_alloc, 0, sizeof(control_alloc));
+ memset(&dma_alloc, 0, sizeof(dma_alloc));
+ is_control_page_mapped = false;
+ is_dma_page_mapped = false;
+ interrupt_signaled = false;
+ transfer_in_progress = false;
+ control_physical_frame = 0;
+ dma_physical_frame = 0;
+}
+
+Port::~Port()
+{
+ if ( transfer_in_progress )
+ FinishTransferDMA();
+ if ( is_control_page_mapped )
+ {
+ Memory::Unmap(control_alloc.from);
+ Memory::Flush();
+ }
+ if ( is_dma_page_mapped )
+ {
+ Memory::Unmap(dma_alloc.from);
+ Memory::Flush();
+ }
+ FreeKernelAddress(&control_alloc);
+ FreeKernelAddress(&dma_alloc);
+ if ( control_physical_frame )
+ Page::Put(control_physical_frame, PAGE_USAGE_DRIVER);
+ if ( dma_physical_frame )
+ Page::Put(dma_physical_frame, PAGE_USAGE_DRIVER);
+}
+
+void Port::LogF(const char* format, ...)
+{
+ // TODO: Print this line in an atomic manner.
+ const char* cdesc = channel->channel_index == 0 ? "primary" : "secondary";
+ const char* ddesc = port_index == 0 ? "master" : "slave";
+ Log::PrintF("ata: pci 0x%X: %s %s: ", channel->devaddr, cdesc, ddesc);
+ va_list ap;
+ va_start(ap, format);
+ Log::PrintFV(format, ap);
+ va_end(ap);
+ Log::PrintF("\n");
+}
+
+bool Port::Initialize()
+{
+ if ( !(control_physical_frame = Page::Get32Bit(PAGE_USAGE_DRIVER)) )
+ {
+ LogF("error: control page allocation failure");
+ return false;
+ }
+
+ if ( !(dma_physical_frame = Page::Get32Bit(PAGE_USAGE_DRIVER)) )
+ {
+ LogF("error: dma page allocation failure");
+ return false;
+ }
+
+ if ( !AllocateKernelAddress(&control_alloc, Page::Size()) )
+ {
+ LogF("error: control page virtual address allocation failure");
+ return false;
+ }
+
+ if ( !AllocateKernelAddress(&dma_alloc, Page::Size()) )
+ {
+ LogF("error: dma page virtual address allocation failure");
+ return false;
+ }
+
+ int prot = PROT_KREAD | PROT_KWRITE;
+
+ is_control_page_mapped =
+ Memory::Map(control_physical_frame, control_alloc.from, prot);
+ if ( !is_control_page_mapped )
+ {
+ LogF("error: control page virtual address allocation failure");
+ return false;
+ }
+
+ Memory::Flush();
+
+ is_dma_page_mapped = Memory::Map(dma_physical_frame, dma_alloc.from, prot);
+ if ( !is_dma_page_mapped )
+ {
+ LogF("error: dma page virtual address allocation failure");
+ return false;
+ }
+
+ Memory::Flush();
+
+ prdt = (volatile struct prd*) (control_alloc.from);
+
+ return true;
+}
+
+bool Port::FinishInitialize()
+{
+ ScopedLock lock(&channel->hw_lock);
+
+ channel->SelectDrive(port_index);
+
+ outport8(channel->port_base + REG_COMMAND, CMD_IDENTIFY);
+
+ sleep_400_nanoseconds();
+
+ // TODO: The status polling logic should be double-checked against some
+ // formal specification telling how this should properly be done.
+
+ uint8_t status = inport8(channel->port_base + REG_STATUS);
+ if ( status == 0 )
+ {
+ // Non-existent.
+ return errno = ENODEV, false;
+ }
+
+ // TODO: This failing might mean non-existent.
+ // TODO: What is a good timeout here?
+ if ( !wait_inport8_clear(channel->port_base + REG_STATUS, STATUS_BUSY,
+ false, 1000 /*ms*/) )
+ {
+ // IDENTIFY timed out, still busy.
+ return errno = ETIMEDOUT, false;
+ }
+
+ // TODO: This failing might mean non-existent.
+ // TODO: Should we actually wait here, or are the status set already?
+ // TODO: What is a good timeout here?
+ if ( !wait_inport8_set(channel->port_base + REG_STATUS,
+ STATUS_DATAREADY | STATUS_ERROR, true, 1000 /*ms*/) )
+ {
+ // IDENTIFY timed out, status not set.
+ return errno = ETIMEDOUT, false;
+ }
+
+ status = inport8(channel->port_base + REG_STATUS);
+ if ( status & STATUS_ERROR )
+ {
+ uint8_t mid = inport8(channel->port_base + REG_LBA_MID);
+ uint8_t high = inport8(channel->port_base + REG_LBA_HIGH);
+
+ if ( (mid == 0x14 && high == 0xEB) || (mid == 0x69 && high == 0x96) )
+ {
+ // TODO: Add ATAPI support.
+ //LogF("ignoring: found ATAPI device instead");
+ return errno = ENODRV, false;
+ }
+ else if ( mid == 0x3C && high == 0xC3 )
+ {
+ // TODO: Does this actually happen and can we do something?
+ //LogF("ignoring: found SATA device instead");
+ return errno = ENODRV, false;
+ }
+ else if ( (mid || high) && (mid != 0x7F && high != 0x7F) )
+ {
+ //LogF("ignoring: found unknown device instead (0x%02X:0x%02X)",
+ // mid, high);
+ return errno = ENODRV, false;
+ }
+ else if ( mid == 0x7F && high == 0x7F )
+ {
+ //LogF("ignoring: non-existent");
+ return errno = EIO, false;
+ }
+ else
+ {
+ LogF("ignoring: IDENTIFY returned error status");
+ return errno = EIO, false;
+ }
+ }
+
+ for ( size_t i = 0; i < 256; i++ )
+ {
+ uint16_t value = inport16(channel->port_base + REG_DATA);
+ identify_data[2*i + 0] = value >> 0 & 0xFF;
+ identify_data[2*i + 1] = value >> 8 & 0xFF;
+ }
+
+ little_uint16_t* words = (little_uint16_t*) identify_data;
+
+ if ( words[0] & (1 << 15) )
+ return errno = EINVAL, false; // Skipping non-ATA device.
+ if ( !(words[49] & (1 << 9)) )
+ return errno = EINVAL, false; // Skipping non-LBA device.
+
+ this->is_lba48 = words[83] & (1 << 10);
+
+ copy_ata_string(serial, (const char*) &words[10], sizeof(serial) - 1);
+ copy_ata_string(revision, (const char*) &words[23], sizeof(revision) - 1);
+ copy_ata_string(model, (const char*) &words[27], sizeof(model) - 1);
+
+ uint64_t block_count;
+ if ( is_lba48 )
+ {
+ block_count = (uint64_t) words[100] << 0 |
+ (uint64_t) words[101] << 16 |
+ (uint64_t) words[102] << 32 |
+ (uint64_t) words[103] << 48;
+ }
+ else
+ {
+ block_count = (uint64_t) words[60] << 0 |
+ (uint64_t) words[61] << 16;
+ }
+
+ uint64_t block_size = 512;
+ if( (words[106] & (1 << 14)) &&
+ !(words[106] & (1 << 15)) &&
+ (words[106] & (1 << 12)) )
+ {
+ block_size = 2 * ((uint64_t) words[117] << 0 |
+ (uint64_t) words[118] << 16);
+ }
+
+ // TODO: Verify the block size is a power of two.
+
+ cylinder_count = words[1];
+ head_count = words[3];
+ sector_count = words[6];
+
+ is_using_dma = true;
+
+ // This is apparently the case on older hardware, there are additional
+ // restrictions on DMA usage, we could work around it, but instead we just
+ // don't use DMA at all, as such hardware should be irrelevant by now. Print
+ // a warning so we can look into this if it actually turns out to matter.
+ // TODO: Arg. This happens in VirtualBox!
+ // TODO: Alternative solution. If this is true, then it should lock the
+ // HBA hw_lock instead than the channel one.
+ uint8_t bm_status = inport8(channel->busmaster_base + BUSMASTER_REG_STATUS);
+ if ( bm_status & BUSMASTER_STATUS_SIMPLEX )
+ {
+ LogF("warning: simplex silliness: no DMA");
+ is_using_dma = false;
+ }
+
+ // TODO: Why is this commented out? Due to the above?
+#if 0
+ if ( port_index == 0 && !(bm_status & BUSMASTER_STATUS_MASTER_DMA_INIT) )
+ {
+ LogF("warning: no DMA support");
+ is_using_dma = false;
+ }
+
+ if ( port_index == 1 && !(bm_status & BUSMASTER_STATUS_SLAVE_DMA_INIT) )
+ {
+ LogF("warning: no DMA support");
+ is_using_dma = false;
+ }
+#endif
+
+ if ( __builtin_mul_overflow(block_count, block_size, &this->device_size) )
+ {
+ LogF("error: device size overflows off_t");
+ return errno = EOVERFLOW, false;
+ }
+
+ this->block_count = (blkcnt_t) block_count;
+ this->block_size = (blkcnt_t) block_size;
+
+ return true;
+}
+
+void Port::Seek(blkcnt_t block_index, size_t count)
+{
+ uintmax_t lba = (uintmax_t) block_index;
+ uint8_t mode = (is_lba48 ? 0x40 : 0xE0) | port_index << 4;
+ outport8(channel->port_base + REG_DRIVE_SELECT, mode);
+ if ( is_lba48 )
+ {
+ outport8(channel->port_base + REG_SECTOR_COUNT, count >> 8 & 0xFF);
+ outport8(channel->port_base + REG_LBA_LOW, lba >> 24 & 0xFF);
+ outport8(channel->port_base + REG_LBA_MID, lba >> 32 & 0xFF);
+ outport8(channel->port_base + REG_LBA_HIGH, lba >> 40 & 0xFF);
+ }
+ outport8(channel->port_base + REG_SECTOR_COUNT, count >> 0 & 0xFF);
+ outport8(channel->port_base + REG_LBA_LOW, lba >> 0 & 0xFF);
+ outport8(channel->port_base + REG_LBA_MID, lba >> 8 & 0xFF);
+ outport8(channel->port_base + REG_LBA_HIGH, lba >> 16 & 0xFF);
+}
+
+void Port::CommandDMA(uint8_t cmd, size_t size, bool write)
+{
+ assert(size);
+ assert(size <= Page::Size());
+ assert(size <= UINT16_MAX);
+ assert((size & 1) == 0); /* sizes and addresses must be 2-byte aligned */
+
+ // Store the DMA region in the first PRD.
+ prdt->physical = dma_physical_frame >> 0 & 0xFFFFFFFF;
+ prdt->count = size;
+ prdt->flags = PRD_FLAG_EOT;
+
+ // Tell the hardware the location of the PRDT.
+ uint32_t bm_prdt = control_physical_frame >> 0 & 0xFFFFFFFF;
+ outport32(channel->busmaster_base + BUSMASTER_REG_PDRT, bm_prdt);
+
+ // Clear the error and interrupt bits.
+ uint8_t bm_status = inport8(channel->busmaster_base + BUSMASTER_REG_STATUS);
+ bm_status |= BUSMASTER_STATUS_DMA_FAILURE | BUSMASTER_STATUS_INTERRUPT_PENDING;
+ outport8(channel->busmaster_base + BUSMASTER_REG_STATUS, bm_status);
+
+ // Set the transfer direction
+ uint8_t bm_command = inport8(channel->busmaster_base + BUSMASTER_REG_COMMAND);
+ if ( write )
+ bm_command &= ~BUSMASTER_COMMAND_READING;
+ else
+ bm_command |= BUSMASTER_COMMAND_READING;
+ outport8(channel->busmaster_base + BUSMASTER_REG_COMMAND, bm_command);
+
+ // Anticipate we will be delivered an IRQ.
+ PrepareAwaitInterrupt();
+ transfer_in_progress = true;
+ transfer_size = size;
+ transfer_is_write = write;
+
+ // Execute the command.
+ outport8(channel->port_base + REG_COMMAND, cmd);
+
+ // Start the DMA transfer.
+ bm_command = inport8(channel->busmaster_base + BUSMASTER_REG_COMMAND);
+ bm_command |= BUSMASTER_COMMAND_START;
+ outport8(channel->busmaster_base + BUSMASTER_REG_COMMAND, bm_command);
+}
+
+bool Port::FinishTransferDMA()
+{
+ assert(transfer_in_progress);
+
+ bool result = true;
+
+ // Wait for an interrupt to arrive.
+ if ( !AwaitInterrupt(10000 /*ms*/) )
+ {
+ const char* op = transfer_is_write ? "write" : "read";
+ LogF("error: %s DMA timed out", op);
+ errno = EIO;
+ result = false;
+ // TODO: Is this a consistent state, do we need a reset, how to recover?
+ }
+
+ // Stop the DMA transfer.
+ uint8_t bm_command = inport8(channel->busmaster_base + BUSMASTER_REG_COMMAND);
+ bm_command &= ~BUSMASTER_COMMAND_START;
+ outport8(channel->busmaster_base + BUSMASTER_REG_COMMAND, bm_command);
+
+ // Clear the error and interrupt bits.
+ uint8_t bm_status = inport8(channel->busmaster_base + BUSMASTER_REG_STATUS);
+ uint8_t bm_set_flags = BUSMASTER_STATUS_DMA_FAILURE |
+ BUSMASTER_STATUS_INTERRUPT_PENDING;
+ outport8(channel->busmaster_base + BUSMASTER_REG_STATUS,
+ bm_status | bm_set_flags);
+
+ // Check if the DMA transfer failed.
+ if ( bm_status & BUSMASTER_STATUS_DMA_FAILURE )
+ {
+ const char* op = transfer_is_write ? "write" : "read";
+ LogF("error: %s DMA error", op);
+ errno = EIO;
+ result = false;
+ // TODO: Is this a consistent state, do we need a reset, how to recover?
+ }
+
+ transfer_in_progress = false;
+ return result;
+}
+
+void Port::CommandPIO(uint8_t cmd, size_t size, bool write)
+{
+ assert(size);
+ assert(size <= Page::Size());
+ assert((size & 1) == 0); /* sizes and addresses must be 2-byte aligned */
+
+ // Anticipate we will be delivered an IRQ.
+ PrepareAwaitInterrupt();
+
+ // Execute the command.
+ (void) write;
+ outport8(channel->port_base + REG_COMMAND, cmd);
+}
+
+bool Port::TransferPIO(size_t size, bool write)
+{
+ const char* op = write ? "write" : "read";
+ size_t i = 0;
+ while ( write || true )
+ {
+ if ( !write && i < size && !AwaitInterrupt(10000 /*ms*/) )
+ {
+ LogF("error: %s timed out, waiting for transfer start", op);
+ return errno = EIO, false;
+ }
+
+ if ( !wait_inport8_clear(channel->port_base + REG_STATUS,
+ STATUS_BUSY, false, 10000 /*ms*/) )
+ {
+ LogF("error: %s timed out waiting for transfer pre idle", op);
+ return errno = EIO, false;
+ }
+
+ uint8_t status = inport8(channel->port_base + REG_STATUS);
+
+ if ( status & STATUS_BUSY )
+ {
+ LogF("error: %s unexpectedly still busy", op);
+ return errno = EIO, false;
+ }
+
+ if ( status & (STATUS_ERROR | STATUS_DRIVEFAULT) )
+ {
+ LogF("error: %s error", op);
+ return errno = EIO, false;
+ }
+
+ if ( i == size )
+ break;
+
+ if ( !(status & STATUS_DATAREADY) )
+ {
+ LogF("error: %s unexpectedly not ready", op);
+ return errno = EIO, false;
+ }
+
+ // Anticipate another IRQ if we're not at the end.
+ size_t i_sector_end = i + block_size;
+ if ( i_sector_end != size )
+ PrepareAwaitInterrupt();
+
+ uint8_t* dma_data = (uint8_t*) dma_alloc.from;
+
+ if ( write )
+ {
+ while ( i < size && i < i_sector_end )
+ {
+ uint16_t value = dma_data[i + 0] << 0 | dma_data[i + 1] << 8;
+ outport16(channel->port_base + REG_DATA, value);
+ i += 2;
+ }
+ }
+ else
+ {
+ while ( i < size && i < i_sector_end )
+ {
+ uint16_t value = inport16(channel->port_base + REG_DATA);
+ dma_data[i + 0] = (value >> 0) & 0xFF;
+ dma_data[i + 1] = (value >> 8) & 0xFF;
+ i += 2;
+ }
+ }
+
+ if ( write && !AwaitInterrupt(10000 /*ms*/) )
+ {
+ LogF("error: %s timed out, waiting for transfer end", op);
+ return errno = EIO, false;
+ }
+ }
+
+ return true;
+}
+
+off_t Port::GetSize()
+{
+ return device_size;
+}
+
+blkcnt_t Port::GetBlockCount()
+{
+ return block_count;
+}
+
+blksize_t Port::GetBlockSize()
+{
+ return block_size;
+}
+
+uint16_t Port::GetCylinderCount()
+{
+ return cylinder_count;
+}
+
+uint16_t Port::GetHeadCount()
+{
+ return head_count;
+}
+
+uint16_t Port::GetSectorCount()
+{
+ return sector_count;
+}
+
+const char* Port::GetDriver()
+{
+ return "ata";
+}
+
+const char* Port::GetModel()
+{
+ return model;
+}
+
+const char* Port::GetSerial()
+{
+ return serial;
+}
+
+const char* Port::GetRevision()
+{
+ return revision;
+}
+
+const unsigned char* Port::GetATAIdentify(size_t* size_ptr)
+{
+ return *size_ptr = sizeof(identify_data), identify_data;
+}
+
+int Port::sync(ioctx_t* ctx)
+{
+ (void) ctx;
+ ScopedLock lock(&channel->hw_lock);
+ channel->SelectDrive(port_index);
+ if ( transfer_in_progress && !FinishTransferDMA() )
+ return -1;
+ PrepareAwaitInterrupt();
+ uint8_t cmd = is_lba48 ? CMD_FLUSH_CACHE_EXT : CMD_FLUSH_CACHE;
+ outport8(channel->port_base + REG_COMMAND, cmd);
+ // TODO: This might take longer than 30 seconds according to the spec. But
+ // how long? Let's say twice that?
+ if ( !AwaitInterrupt(2 * 30000 /*ms*/) )
+ {
+ LogF("error: cache flush timed out");
+ transfer_in_progress = false;
+ return errno = EIO, -1;
+ }
+ transfer_in_progress = false;
+ uint8_t status = inport8(channel->port_base + REG_STATUS);
+ if ( status & (STATUS_ERROR | STATUS_DRIVEFAULT) )
+ {
+ LogF("error: IO error");
+ return errno = EIO, -1;
+ }
+ return 0;
+}
+
+ssize_t Port::pread(ioctx_t* ctx, unsigned char* buf, size_t count, off_t off)
+{
+ ssize_t result = 0;
+ while ( count )
+ {
+ ScopedLock lock(&channel->hw_lock);
+ channel->SelectDrive(port_index);
+ if ( device_size <= off )
+ break;
+ if ( (uintmax_t) device_size - off < (uintmax_t) count )
+ count = (size_t) device_size - off;
+ uintmax_t block_index = (uintmax_t) off / (uintmax_t) block_size;
+ uintmax_t block_offset = (uintmax_t) off % (uintmax_t) block_size;
+ uintmax_t amount = block_offset + count;
+ if ( Page::Size() < amount )
+ amount = Page::Size();
+ size_t num_blocks = (amount + block_size - 1) / block_size;
+ uintmax_t full_amount = num_blocks * block_size;
+ // If an asynchronous operation is in progress, let it finish.
+ if ( transfer_in_progress && !FinishTransferDMA() )
+ return result ? result : -1;
+ unsigned char* dma_data = (unsigned char*) dma_alloc.from;
+ unsigned char* data = dma_data + block_offset;
+ size_t data_size = amount - block_offset;
+ Seek(block_index, num_blocks);
+ if ( is_using_dma )
+ {
+ uint8_t cmd = is_lba48 ? CMD_READ_DMA_EXT : CMD_READ_DMA;
+ CommandDMA(cmd, (size_t) full_amount, false);
+ if ( !FinishTransferDMA() )
+ return result ? result : -1;
+ }
+ else
+ {
+ uint8_t cmd = is_lba48 ? CMD_READ_EXT : CMD_READ;
+ CommandPIO(cmd, (size_t) full_amount, false);
+ if ( !TransferPIO((size_t) full_amount, false) )
+ return result ? result : -1;
+ }
+ if ( !ctx->copy_to_dest(buf, data, data_size) )
+ return result ? result : -1;
+ buf += data_size;
+ count -= data_size;
+ result += data_size;
+ off += data_size;
+ }
+ return result;
+}
+
+ssize_t Port::pwrite(ioctx_t* ctx, const unsigned char* buf, size_t count, off_t off)
+{
+ ssize_t result = 0;
+ while ( count )
+ {
+ ScopedLock lock(&channel->hw_lock);
+ channel->SelectDrive(port_index);
+ if ( device_size <= off )
+ break;
+ if ( (uintmax_t) device_size - off < (uintmax_t) count )
+ count = (size_t) device_size - off;
+ uintmax_t block_index = (uintmax_t) off / (uintmax_t) block_size;
+ uintmax_t block_offset = (uintmax_t) off % (uintmax_t) block_size;
+ uintmax_t amount = block_offset + count;
+ if ( Page::Size() < amount )
+ amount = Page::Size();
+ size_t num_blocks = (amount + block_size - 1) / block_size;
+ uintmax_t full_amount = num_blocks * block_size;
+ // If an asynchronous operation is in progress, let it finish.
+ if ( transfer_in_progress && !FinishTransferDMA() )
+ return result ? result : -1;
+ unsigned char* dma_data = (unsigned char*) dma_alloc.from;
+ unsigned char* data = dma_data + block_offset;
+ size_t data_size = amount - block_offset;
+ if ( block_offset || amount < full_amount )
+ {
+ if ( is_using_dma )
+ {
+ uint8_t cmd = is_lba48 ? CMD_READ_DMA_EXT : CMD_READ_DMA;
+ Seek(block_index, num_blocks);
+ CommandDMA(cmd, (size_t) full_amount, false);
+ if ( !FinishTransferDMA() )
+ return result ? result : -1;
+ }
+ else
+ {
+ uint8_t cmd = is_lba48 ? CMD_READ_EXT : CMD_READ;
+ Seek(block_index, num_blocks);
+ CommandPIO(cmd, (size_t) full_amount, false);
+ if ( !TransferPIO((size_t) full_amount, false) )
+ return result ? result : -1;
+ }
+ }
+ if ( !ctx->copy_from_src(data, buf, data_size) )
+ return result ? result : -1;
+ if ( is_using_dma )
+ {
+ Seek(block_index, num_blocks);
+ uint8_t cmd = is_lba48 ? CMD_WRITE_DMA_EXT : CMD_WRITE_DMA;
+ CommandDMA(cmd, (size_t) full_amount, true);
+ // Let the transfer finish asynchronously so the caller can prepare
+ // the next write operation to keep the write pipeline busy.
+ }
+ else
+ {
+ Seek(block_index, num_blocks);
+ uint8_t cmd = is_lba48 ? CMD_WRITE_EXT : CMD_WRITE;
+ CommandPIO(cmd, (size_t) full_amount, true);
+ if ( !TransferPIO((size_t) full_amount, true) )
+ return result ? result : -1;
+ }
+ buf += data_size;
+ count -= data_size;
+ result += data_size;
+ off += data_size;
+ }
+ return result;
+}
+
+void Port::PrepareAwaitInterrupt()
+{
+ interrupt_signaled = false;
+}
+
+bool Port::AwaitInterrupt(unsigned int msecs)
+{
+ struct timespec timeout = timespec_make(msecs / 1000, (msecs % 1000) * 1000000L);
+ Clock* clock = Time::GetClock(CLOCK_BOOT);
+ struct timespec begun;
+ clock->Get(&begun, NULL);
+ while ( true )
+ {
+ struct timespec now;
+ clock->Get(&now, NULL);
+ if ( interrupt_signaled )
+ return true;
+ struct timespec elapsed = timespec_sub(now, begun);
+ if ( timespec_le(timeout, elapsed) )
+ return errno = ETIMEDOUT, false;
+ // TODO: Can't safely back out here unless the pending operation is
+ // is properly cancelled.
+ //if ( Signal::IsPending() )
+ // return errno = EINTR, false;
+ kthread_yield();
+ }
+}
+
+void Port::OnInterrupt()
+{
+ // Clear INTRQ.
+ uint8_t status = inport8(channel->port_base + REG_STATUS);
+
+ if ( status & STATUS_ERROR )
+ {
+ uint8_t error_status = inport8(channel->port_base + REG_ERROR);
+ (void) error_status; // TODO: How to handle this exactly?
+ }
+
+ if ( !interrupt_signaled )
+ {
+ interrupt_signaled = true;
+ // TODO: Priority schedule the blocking thread now.
+ }
+}
+
+} // namespace ATA
+} // namespace Sortix
diff --git a/kernel/disk/ata/port.h b/kernel/disk/ata/port.h
new file mode 100644
index 00000000..6287eddd
--- /dev/null
+++ b/kernel/disk/ata/port.h
@@ -0,0 +1,114 @@
+/*******************************************************************************
+
+ Copyright(C) Jonas 'Sortie' Termansen 2011, 2012, 2013, 2014, 2015, 2016.
+
+ This file is part of Sortix.
+
+ Sortix is free software: you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation, either version 3 of the License, or (at your option) any later
+ version.
+
+ Sortix is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ details.
+
+ You should have received a copy of the GNU General Public License along with
+ Sortix. If not, see .
+
+ disk/ata/port.h
+ Driver for ATA.
+
+*******************************************************************************/
+
+#ifndef SORTIX_DISK_ATA_PORT_H
+#define SORTIX_DISK_ATA_PORT_H
+
+#include
+
+#include
+
+#include
+#include
+#include
+#include
+
+namespace Sortix {
+namespace ATA {
+
+class Channel;
+
+class Port : public Harddisk
+{
+ friend class Channel;
+
+public:
+ Port(Channel* channel, unsigned int port_index);
+ virtual ~Port();
+
+public:
+ virtual off_t GetSize();
+ virtual blkcnt_t GetBlockCount();
+ virtual blksize_t GetBlockSize();
+ virtual uint16_t GetCylinderCount();
+ virtual uint16_t GetHeadCount();
+ virtual uint16_t GetSectorCount();
+ virtual const char* GetDriver();
+ virtual const char* GetModel();
+ virtual const char* GetSerial();
+ virtual const char* GetRevision();
+ virtual const unsigned char* GetATAIdentify(size_t* size_ptr);
+ virtual int sync(ioctx_t* ctx);
+ virtual ssize_t pread(ioctx_t* ctx, unsigned char* buf, size_t count, off_t off);
+ virtual ssize_t pwrite(ioctx_t* ctx, const unsigned char* buf, size_t count, off_t off);
+
+public:
+ bool Initialize();
+ bool FinishInitialize();
+
+private:
+ __attribute__((format(printf, 2, 3)))
+ void LogF(const char* format, ...);
+ void Seek(blkcnt_t block_index, size_t count);
+ void CommandDMA(uint8_t cmd, size_t size, bool write);
+ void CommandPIO(uint8_t cmd, size_t size, bool write);
+ bool FinishTransferDMA();
+ bool TransferPIO(size_t size, bool write);
+ void PrepareAwaitInterrupt();
+ bool AwaitInterrupt(unsigned int msescs);
+ void OnInterrupt();
+
+private:
+ unsigned char identify_data[512];
+ char serial[20 + 1];
+ char revision[8 + 1];
+ char model[40 + 1];
+ addralloc_t control_alloc;
+ addralloc_t dma_alloc;
+ Channel* channel;
+ volatile struct prd* prdt;
+ addr_t control_physical_frame;
+ addr_t dma_physical_frame;
+ unsigned int port_index;
+ bool is_control_page_mapped;
+ bool is_dma_page_mapped;
+ bool is_lba48;
+ bool is_using_dma;
+ off_t device_size;
+ blksize_t block_count;
+ blkcnt_t block_size;
+ uint16_t cylinder_count;
+ uint16_t head_count;
+ uint16_t sector_count;
+ volatile bool interrupt_signaled;
+ bool transfer_in_progress;
+ size_t transfer_size;
+ bool transfer_is_write;
+
+};
+
+} // namespace ATA
+} // namespace Sortix
+
+#endif
diff --git a/kernel/disk/ata/registers.h b/kernel/disk/ata/registers.h
new file mode 100644
index 00000000..b37dbf00
--- /dev/null
+++ b/kernel/disk/ata/registers.h
@@ -0,0 +1,88 @@
+/*******************************************************************************
+
+ Copyright(C) Jonas 'Sortie' Termansen 2011, 2012, 2013, 2014, 2015, 2016.
+
+ This file is part of Sortix.
+
+ Sortix is free software: you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation, either version 3 of the License, or (at your option) any later
+ version.
+
+ Sortix is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ details.
+
+ You should have received a copy of the GNU General Public License along with
+ Sortix. If not, see .
+
+ disk/ata/registers.h
+ Driver for ATA.
+
+*******************************************************************************/
+
+#ifndef SORTIX_DISK_ATA_REGISTERS_H
+#define SORTIX_DISK_ATA_REGISTERS_H
+
+#include
+#include
+
+namespace Sortix {
+namespace ATA {
+
+struct prd
+{
+ little_uint32_t physical;
+ little_uint16_t count;
+ little_uint16_t flags;
+};
+
+static const uint16_t PRD_FLAG_EOT = 1 << 15;
+
+static const uint16_t REG_DATA = 0x0;
+static const uint16_t REG_FEATURE = 0x1;
+static const uint16_t REG_ERROR = 0x1;
+static const uint16_t REG_SECTOR_COUNT = 0x2;
+static const uint16_t REG_LBA_LOW = 0x3;
+static const uint16_t REG_LBA_MID = 0x4;
+static const uint16_t REG_LBA_HIGH = 0x5;
+static const uint16_t REG_DRIVE_SELECT = 0x6;
+static const uint16_t REG_COMMAND = 0x7;
+static const uint16_t REG_STATUS = 0x7;
+
+static const uint8_t CMD_READ = 0x20;
+static const uint8_t CMD_READ_EXT = 0x24;
+static const uint8_t CMD_READ_DMA = 0xC8;
+static const uint8_t CMD_READ_DMA_EXT = 0x25;
+static const uint8_t CMD_WRITE = 0x30;
+static const uint8_t CMD_WRITE_EXT = 0x34;
+static const uint8_t CMD_WRITE_DMA = 0xCA;
+static const uint8_t CMD_WRITE_DMA_EXT = 0x35;
+static const uint8_t CMD_FLUSH_CACHE = 0xE7;
+static const uint8_t CMD_FLUSH_CACHE_EXT = 0xEA;
+static const uint8_t CMD_IDENTIFY = 0xEC;
+
+static const uint8_t STATUS_ERROR = 1 << 0;
+static const uint8_t STATUS_DATAREADY = 1 << 3;
+static const uint8_t STATUS_DRIVEFAULT = 1 << 5;
+static const uint8_t STATUS_BUSY = 1 << 7;
+
+static const uint16_t BUSMASTER_REG_COMMAND = 0x0;
+static const uint16_t BUSMASTER_REG_STATUS = 0x2;
+static const uint16_t BUSMASTER_REG_PDRT = 0x4;
+
+static const uint16_t BUSMASTER_COMMAND_START = 1 << 0;
+static const uint16_t BUSMASTER_COMMAND_READING = 1 << 3;
+
+static const uint8_t BUSMASTER_STATUS_DMA = 1 << 0;
+static const uint8_t BUSMASTER_STATUS_DMA_FAILURE = 1 << 1;
+static const uint8_t BUSMASTER_STATUS_INTERRUPT_PENDING = 1 << 2;
+static const uint8_t BUSMASTER_STATUS_MASTER_DMA_INIT = 1 << 5;
+static const uint8_t BUSMASTER_STATUS_SLAVE_DMA_INIT = 1 << 6;
+static const uint8_t BUSMASTER_STATUS_SIMPLEX = 1 << 7;
+
+} // namespace ATA
+} // namespace Sortix
+
+#endif
diff --git a/kernel/disk/node.cpp b/kernel/disk/node.cpp
new file mode 100644
index 00000000..928cbeaa
--- /dev/null
+++ b/kernel/disk/node.cpp
@@ -0,0 +1,200 @@
+/*******************************************************************************
+
+ Copyright(C) Jonas 'Sortie' Termansen 2011, 2012, 2013, 2014, 2015, 2016.
+
+ This file is part of Sortix.
+
+ Sortix is free software: you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation, either version 3 of the License, or (at your option) any later
+ version.
+
+ Sortix is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ details.
+
+ You should have received a copy of the GNU General Public License along with
+ Sortix. If not, see .
+
+ disk/node.cpp
+ Inode adapter for harddisks.
+
+*******************************************************************************/
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include
+#include
+#include
+#include
+
+#include "node.h"
+
+namespace Sortix {
+
+PortNode::PortNode(Harddisk* harddisk, uid_t owner, gid_t group, mode_t mode,
+ dev_t dev, ino_t /*ino*/)
+{
+ this->harddisk = harddisk;
+ inode_type = INODE_TYPE_FILE;
+ this->dev = dev;
+ this->ino = (ino_t) this;
+ this->stat_uid = owner;
+ this->stat_gid = group;
+ this->type = S_IFBLK;
+ this->stat_size = harddisk->GetSize();
+ this->stat_mode = (mode & S_SETABLE) | this->type;
+ this->stat_blksize = harddisk->GetBlockSize();
+ this->stat_blocks = harddisk->GetBlockCount();
+}
+
+PortNode::~PortNode()
+{
+ // TODO: Ownership of `port'.
+}
+
+int PortNode::sync(ioctx_t* ctx)
+{
+ return harddisk->sync(ctx);
+}
+
+int PortNode::truncate(ioctx_t* /*ctx*/, off_t length)
+{
+ if ( length != harddisk->GetSize() )
+ return errno = EPERM, -1;
+ return 0;
+}
+
+off_t PortNode::lseek(ioctx_t* /*ctx*/, off_t offset, int whence)
+{
+ if ( whence == SEEK_SET )
+ return offset;
+ if ( whence == SEEK_END )
+ {
+ off_t result;
+ if ( __builtin_add_overflow(harddisk->GetSize(), offset, &result) )
+ return errno = EOVERFLOW, -1;
+ return result;
+ }
+ return errno = EINVAL, -1;
+}
+
+ssize_t PortNode::pread(ioctx_t* ctx, uint8_t* buf, size_t count, off_t off)
+{
+ return harddisk->pread(ctx, (unsigned char*) buf, count, off);
+}
+
+ssize_t PortNode::pwrite(ioctx_t* ctx, const uint8_t* buf, size_t count,
+ off_t off)
+{
+ return harddisk->pwrite(ctx, (const unsigned char*) buf, count, off);
+}
+
+ssize_t PortNode::tcgetblob(ioctx_t* ctx, const char* name, void* buffer,
+ size_t count)
+{
+ const void* result_pointer = NULL;
+ size_t result_size = 0;
+ char string[sizeof(uintmax_t) * 3];
+ static const char index[] = "harddisk-driver\0"
+ "harddisk-model\0"
+ "harddisk-serial\0"
+ "harddisk-revision\0"
+ "harddisk-size\0"
+ "harddisk-block-count\0"
+ "harddisk-block-size\0"
+ "harddisk-cylinders\0"
+ "harddisk-heads\0"
+ "harddisk-sectors\0"
+ "harddisk-ata-identify\0";
+
+ if ( !name )
+ {
+ result_pointer = index;
+ result_size = sizeof(index) - 1;
+ }
+ else if ( !strcmp(name, "harddisk-driver") )
+ {
+ result_pointer = harddisk->GetDriver();
+ result_size = strlen((const char*) result_pointer);
+ }
+ else if ( !strcmp(name, "harddisk-model") )
+ {
+ result_pointer = harddisk->GetModel();
+ result_size = strlen((const char*) result_pointer);
+ }
+ else if ( !strcmp(name, "harddisk-serial") )
+ {
+ result_pointer = harddisk->GetSerial();
+ result_size = strlen((const char*) result_pointer);
+ }
+ else if ( !strcmp(name, "harddisk-revision") )
+ {
+ result_pointer = harddisk->GetRevision();
+ result_size = strlen((const char*) result_pointer);
+ }
+ else if ( !strcmp(name, "harddisk-size") )
+ {
+ snprintf(string, sizeof(string), "%" PRIiOFF, harddisk->GetSize());
+ result_pointer = string;
+ result_size = strlen(string);
+ }
+ else if ( !strcmp(name, "harddisk-block-count") )
+ {
+ snprintf(string, sizeof(string), "%" PRIiBLKCNT, harddisk->GetBlockCount());
+ result_pointer = string;
+ result_size = strlen(string);
+ }
+ else if ( !strcmp(name, "harddisk-block-size") )
+ {
+ snprintf(string, sizeof(string), "%" PRIiBLKSIZE, harddisk->GetBlockSize());
+ result_pointer = string;
+ result_size = strlen(string);
+ }
+ else if ( !strcmp(name, "harddisk-cylinders") )
+ {
+ snprintf(string, sizeof(string), "%" PRIu16, harddisk->GetCylinderCount());
+ result_pointer = string;
+ result_size = strlen(string);
+ }
+ else if ( !strcmp(name, "harddisk-heads") )
+ {
+ snprintf(string, sizeof(string), "%" PRIu16, harddisk->GetHeadCount());
+ result_pointer = string;
+ result_size = strlen(string);
+ }
+ else if ( !strcmp(name, "harddisk-sectors") )
+ {
+ snprintf(string, sizeof(string), "%" PRIu16, harddisk->GetSectorCount());
+ result_pointer = string;
+ result_size = strlen(string);
+ }
+ else if ( !strcmp(name, "harddisk-ata-identify") )
+ {
+ if ( !(result_pointer = harddisk->GetATAIdentify(&result_size)) )
+ return -1;
+ }
+ else
+ {
+ return errno = ENOENT, -1;
+ }
+
+ if ( SSIZE_MAX < result_size )
+ return errno = EOVERFLOW, -1;
+ if ( buffer && count < result_size )
+ return errno = ERANGE, -1;
+ if ( buffer && !ctx->copy_to_dest(buffer, result_pointer, result_size) )
+ return -1;
+ return (ssize_t) result_size;
+}
+
+} // namespace Sortix
diff --git a/kernel/disk/node.h b/kernel/disk/node.h
new file mode 100644
index 00000000..e9684067
--- /dev/null
+++ b/kernel/disk/node.h
@@ -0,0 +1,58 @@
+/*******************************************************************************
+
+ Copyright(C) Jonas 'Sortie' Termansen 2011, 2012, 2013, 2014, 2015.
+
+ This file is part of Sortix.
+
+ Sortix is free software: you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation, either version 3 of the License, or (at your option) any later
+ version.
+
+ Sortix is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ details.
+
+ You should have received a copy of the GNU General Public License along with
+ Sortix. If not, see .
+
+ disk/node.h
+ Inode adapter for harddisks.
+
+*******************************************************************************/
+
+#ifndef SORTIX_DISK_NODE_H
+#define SORTIX_DISK_NODE_H
+
+#include
+
+#include
+
+#include
+#include
+
+namespace Sortix {
+
+class Harddisk;
+
+class PortNode : public AbstractInode
+{
+public:
+ PortNode(Harddisk* harddisk, uid_t owner, gid_t group, mode_t mode, dev_t dev, ino_t ino);
+ virtual ~PortNode();
+ virtual int sync(ioctx_t* ctx);
+ virtual int truncate(ioctx_t* ctx, off_t length);
+ virtual off_t lseek(ioctx_t* ctx, off_t offset, int whence);
+ virtual ssize_t pread(ioctx_t* ctx, uint8_t* buf, size_t count, off_t off);
+ virtual ssize_t pwrite(ioctx_t* ctx, const uint8_t* buf, size_t count, off_t off);
+ virtual ssize_t tcgetblob(ioctx_t* ctx, const char* name, void* buffer, size_t count);
+
+private:
+ Harddisk* harddisk;
+
+};
+
+} // namespace Sortix
+
+#endif
diff --git a/kernel/include/sortix/kernel/harddisk.h b/kernel/include/sortix/kernel/harddisk.h
new file mode 100644
index 00000000..f254f8b8
--- /dev/null
+++ b/kernel/include/sortix/kernel/harddisk.h
@@ -0,0 +1,57 @@
+/*******************************************************************************
+
+ Copyright(C) Jonas 'Sortie' Termansen 2015.
+
+ This file is part of Sortix.
+
+ Sortix is free software: you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation, either version 3 of the License, or (at your option) any later
+ version.
+
+ Sortix is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ details.
+
+ You should have received a copy of the GNU General Public License along with
+ Sortix. If not, see .
+
+ sortix/kernel/harddisk.h
+ Harddisk interface.
+
+*******************************************************************************/
+
+#ifndef INCLUDE_SORTIX_KERNEL_HARDDISK_H
+#define INCLUDE_SORTIX_KERNEL_HARDDISK_H
+
+#include
+
+#include
+
+namespace Sortix {
+
+class Harddisk
+{
+public:
+ virtual ~Harddisk() { }
+ virtual off_t GetSize() = 0;
+ virtual blkcnt_t GetBlockCount() = 0;
+ virtual blksize_t GetBlockSize() = 0;
+ virtual uint16_t GetCylinderCount() = 0;
+ virtual uint16_t GetHeadCount() = 0;
+ virtual uint16_t GetSectorCount() = 0;
+ virtual const char* GetDriver() = 0;
+ virtual const char* GetModel() = 0;
+ virtual const char* GetSerial() = 0;
+ virtual const char* GetRevision() = 0;
+ virtual const unsigned char* GetATAIdentify(size_t* size_ptr) = 0;
+ virtual int sync(ioctx_t* ctx) = 0;
+ virtual ssize_t pread(ioctx_t* ctx, unsigned char* buf, size_t count, off_t off) = 0;
+ virtual ssize_t pwrite(ioctx_t* ctx, const unsigned char* buf, size_t count, off_t off) = 0;
+
+};
+
+} // namespace Sortix
+
+#endif
diff --git a/kernel/kernel.cpp b/kernel/kernel.cpp
index 2fcde2f3..c8e0e696 100644
--- a/kernel/kernel.cpp
+++ b/kernel/kernel.cpp
@@ -72,8 +72,8 @@
#include
#include
-#include "ata.h"
#include "com.h"
+#include "disk/ata/ata.h"
#include "fs/full.h"
#include "fs/kram.h"
#include "fs/null.h"