1
0
Fork 0
mirror of https://gitlab.com/sortix/sortix.git synced 2023-02-13 20:55:38 -05:00

Rewrite ATA driver.

This commit is contained in:
Jonas 'Sortie' Termansen 2014-09-28 00:48:09 +02:00
parent c1db172431
commit 79e01c2eba
14 changed files with 1846 additions and 576 deletions

View file

@ -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 \

View file

@ -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 <http://www.gnu.org/licenses/>.
ata.cpp
Allowes access to block devices over ATA PIO.
*******************************************************************************/
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <sortix/stat.h>
#include <sortix/kernel/cpu.h>
#include <sortix/kernel/descriptor.h>
#include <sortix/kernel/inode.h>
#include <sortix/kernel/interlock.h>
#include <sortix/kernel/ioctx.h>
#include <sortix/kernel/ioport.h>
#include <sortix/kernel/kernel.h>
#include <sortix/kernel/kthread.h>
#include <sortix/kernel/refcount.h>
#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<Descriptor> slashdev, unsigned busid,
ATABus* bus, unsigned driveid)
{
unsigned ataid = busid*2 + driveid;
ATADrive* drive = bus->Instatiate(driveid);
if ( !drive )
return;
Ref<ATANode> 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<Descriptor> 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<Descriptor> 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

View file

@ -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 <http://www.gnu.org/licenses/>.
ata.h
Allowes access to block devices over ATA PIO.
*******************************************************************************/
#ifndef SORTIX_ATA_H
#define SORTIX_ATA_H
#include <sortix/kernel/kthread.h>
#include <sortix/kernel/refcount.h>
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<Descriptor> slashdev);
ATABus* CreateBus(uint16_t portoffset, uint16_t altport);
} // namespace ATA
} // namespace Sortix
#endif

66
kernel/disk/ata/ata.cpp Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
disk/ata/ata.cpp
Driver for ATA.
*******************************************************************************/
#include <stdarg.h>
#include <stdint.h>
#include <string.h>
#include <sortix/kernel/descriptor.h>
#include <sortix/kernel/kernel.h>
#include <sortix/kernel/pci.h>
#include <sortix/kernel/refcount.h>
#include "ata.h"
#include "hba.h"
namespace Sortix {
namespace ATA {
static void InitializeDevice(Ref<Descriptor> 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<Descriptor> 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

39
kernel/disk/ata/ata.h Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
disk/ata/ata.h
Driver for ATA.
*******************************************************************************/
#ifndef SORTIX_DISK_ATA_ATA_H
#define SORTIX_DISK_ATA_ATA_H
#include <sortix/kernel/kthread.h>
#include <sortix/kernel/refcount.h>
namespace Sortix {
namespace ATA {
void Init(const char* devpath, Ref<Descriptor> dev);
} // namespace ATA
} // namespace Sortix
#endif

337
kernel/disk/ata/hba.cpp Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
disk/ata/hba.cpp
Driver for ATA.
*******************************************************************************/
#include <errno.h>
#include <stdarg.h>
#include <timespec.h>
#include <sortix/clock.h>
#include <sortix/kernel/clock.h>
#include <sortix/kernel/interlock.h>
#include <sortix/kernel/interrupt.h>
#include <sortix/kernel/ioctx.h>
#include <sortix/kernel/ioport.h>
#include <sortix/kernel/kthread.h>
#include <sortix/kernel/log.h>
#include <sortix/kernel/panic.h>
#include <sortix/kernel/pci.h>
#include <sortix/kernel/time.h>
#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<Descriptor> 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<PortNode> 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<Descriptor> 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<Descriptor> 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

98
kernel/disk/ata/hba.h Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
disk/ata/hba.h
Driver for ATA.
*******************************************************************************/
#ifndef SORTIX_DISK_ATA_HBA_H
#define SORTIX_DISK_ATA_HBA_H
#include <stdint.h>
#include <sortix/kernel/descriptor.h>
#include <sortix/kernel/interrupt.h>
#include <sortix/kernel/kthread.h>
namespace Sortix {
namespace ATA {
class HBA;
class Port;
class Channel
{
friend class Port;
friend class HBA;
public:
Channel();
~Channel();
public:
bool Initialize(Ref<Descriptor> 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<Descriptor> dev, const char* devpath);
private:
bool InitializeChannel(Ref<Descriptor> dev, const char* devpath,
unsigned int channel_index);
private:
uint32_t devaddr;
Channel channels[2];
};
} // namespace ATA
} // namespace Sortix
#endif

784
kernel/disk/ata/port.cpp Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
disk/ata/port.cpp
Driver for ATA.
*******************************************************************************/
#include <assert.h>
#include <errno.h>
#include <stdarg.h>
#include <stdint.h>
#include <endian.h>
#include <timespec.h>
#include <sortix/clock.h>
#include <sortix/mman.h>
#include <sortix/kernel/addralloc.h>
#include <sortix/kernel/clock.h>
#include <sortix/kernel/ioport.h>
#include <sortix/kernel/kthread.h>
#include <sortix/kernel/log.h>
#include <sortix/kernel/memorymanagement.h>
#include <sortix/kernel/signal.h>
#include <sortix/kernel/time.h>
#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

114
kernel/disk/ata/port.h Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
disk/ata/port.h
Driver for ATA.
*******************************************************************************/
#ifndef SORTIX_DISK_ATA_PORT_H
#define SORTIX_DISK_ATA_PORT_H
#include <sys/types.h>
#include <stdint.h>
#include <sortix/kernel/addralloc.h>
#include <sortix/kernel/harddisk.h>
#include <sortix/kernel/ioctx.h>
#include <sortix/kernel/kthread.h>
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

View file

@ -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 <http://www.gnu.org/licenses/>.
disk/ata/registers.h
Driver for ATA.
*******************************************************************************/
#ifndef SORTIX_DISK_ATA_REGISTERS_H
#define SORTIX_DISK_ATA_REGISTERS_H
#include <endian.h>
#include <stdint.h>
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

200
kernel/disk/node.cpp Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
disk/node.cpp
Inode adapter for harddisks.
*******************************************************************************/
#include <sys/types.h>
#include <errno.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sortix/stat.h>
#include <sortix/kernel/harddisk.h>
#include <sortix/kernel/inode.h>
#include <sortix/kernel/kthread.h>
#include <sortix/kernel/log.h>
#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

58
kernel/disk/node.h Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
disk/node.h
Inode adapter for harddisks.
*******************************************************************************/
#ifndef SORTIX_DISK_NODE_H
#define SORTIX_DISK_NODE_H
#include <sys/types.h>
#include <stdint.h>
#include <sortix/kernel/inode.h>
#include <sortix/kernel/kthread.h>
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

View file

@ -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 <http://www.gnu.org/licenses/>.
sortix/kernel/harddisk.h
Harddisk interface.
*******************************************************************************/
#ifndef INCLUDE_SORTIX_KERNEL_HARDDISK_H
#define INCLUDE_SORTIX_KERNEL_HARDDISK_H
#include <sys/types.h>
#include <sortix/kernel/ioctx.h>
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

View file

@ -72,8 +72,8 @@
#include <sortix/kernel/vnode.h>
#include <sortix/kernel/worker.h>
#include "ata.h"
#include "com.h"
#include "disk/ata/ata.h"
#include "fs/full.h"
#include "fs/kram.h"
#include "fs/null.h"