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:
parent
c1db172431
commit
79e01c2eba
14 changed files with 1846 additions and 576 deletions
|
@ -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 \
|
||||
|
|
477
kernel/ata.cpp
477
kernel/ata.cpp
|
@ -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
|
95
kernel/ata.h
95
kernel/ata.h
|
@ -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
66
kernel/disk/ata/ata.cpp
Normal 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
39
kernel/disk/ata/ata.h
Normal 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
337
kernel/disk/ata/hba.cpp
Normal 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
98
kernel/disk/ata/hba.h
Normal 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
784
kernel/disk/ata/port.cpp
Normal 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
114
kernel/disk/ata/port.h
Normal 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
|
88
kernel/disk/ata/registers.h
Normal file
88
kernel/disk/ata/registers.h
Normal 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
200
kernel/disk/node.cpp
Normal 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
58
kernel/disk/node.h
Normal 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
|
57
kernel/include/sortix/kernel/harddisk.h
Normal file
57
kernel/include/sortix/kernel/harddisk.h
Normal 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
|
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue