l4-hurd
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

The need for an IDE driver


From: Matthieu Lemerre
Subject: The need for an IDE driver
Date: Tue, 31 May 2005 22:11:17 +0200
User-agent: Gnus/5.11 (Gnus v5.11) Emacs/22.0.50 (gnu/linux)

Hi,

There is a need to access a hard drive disk from the Hurd, so a need
for an IDE driver.

The best would be to have it available in the device driver framework;
however the device driver framework is not ready yet.

I invistigated the different possibilities we have:

* The first solution would be to have a Linux kernel running in
  parallel of the Hurd.  Once we get that, we would just have to
  implement threads waiting for RPCs (from deva) and processing the
  request.

** This could be done by having L4Linux running as a normal task,
 launched by wortel. To do that, it seems that there is just a set of
 interfaces (l4lxlib) to implement. This require a substancial amount
 of work and maintainance. Moreover, it seems that it would be more
 difficult to do this for l4linux2.6; and the l4linux project is not
 supported anymore by the L4 team.

** The most promising approach would be to "reuse unmodified device
   drivers by running them within their original operating system in a
   virtual machine" from the L4ka project
   (http://www.l4ka.org/projects/virtualization/drivers.php), maybe in
   conjonction with their previrtualization project. But source code
   for this project does not seem to be available;

* Other possibilities would be to use the different device driver
  libraries for L4 that exist. There is at least two:

** L4-env and their DDE
   (http://os.inf.tu-dresden.de/l4env/download.xml) 

** Kenge (http://www.disy.cse.unsw.edu.au/Software/Kenge/)

I don't know how difficult it would be to link them to deva; I can
investigate more on this.

For the moment, I hacked a tiny IDE driver from the KOS project
(http://kos.enix.org).  It works: I can read blocs of my emulated hard
drive with qemu. However, as its original authors (Thomas Petazonni
and David Decotigny) says, there is no manegement for DMA, IRQ,
multi-bloc requests, and the detection of the IDE controller and disks
do not always work.

I don't have real hardware to do the test; maybe there would be timers
to adapt. There should be a better timer function; it is rather
imprecise.

The original authors are willing to give their copyright to the FSF if
necessary.

I put the source code files as an attachment.  Communication with this
driver from deva can be done by including "ide.h".

Thanks,
Matthieu

/* ide.c - Interface between KOS's ide driver and deva.

   Copyright (C) 2005  Free Software Foundation, Inc.
   Adapted for the Hurd by Matthieu Lemerre.

   Derived from _ide_karm.c from the KOS project, which is
   Copyright (C) 2000-2005, Thomas Petazzoni <address@hidden>
   Copyright (C) 2000-2005, David Decotigny <address@hidden>
   
   This file is part of the GNU Hurd.

   The GNU Hurd is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   The GNU Hurd 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
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with the GNU C Library; if not, write to the Free
   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
   02111-1307 USA.  */

#include "env.h"
#include "_ide.h"

#define _GNU_SOURCE 1
#include <errno.h>
#include <assert.h>

extern ide_device_t *ide_devices[IDE_MAX_CTRL][IDE_MAX_DEV_PER_CTRL];

error_t ide_block_read(int controller_nb, int device_nb,
                       char *buffer,  count_t block_start,
                       count_t *inout_block_count)
{
  ide_device_t *device;
  count_t block_i;
  result_t result;

  device = (ide_device_t *) ide_devices[controller_nb][device_nb];

  assert (device != NULL);

  for (block_i = block_start ; 
       (block_i < (block_start + *inout_block_count))
         && (block_i < device->block_count) ;
       block_i++, buffer += device->blk_size)
    {
      result = _ide_io_polled_operation(device, IDE_READ, block_i,
                                        (k_ui16_t *) buffer);
      if(result < 0) 
        {
          return result;
        }
    }

  *inout_block_count = block_i - block_start;
  return ESUCCESS;
}

error_t ide_block_write(int controller_nb, int device_nb,
                        const char *buffer, count_t block_start,
                        count_t *inout_block_count)
{
  ide_device_t *device;
  count_t block_i;
  result_t result;

  device = (ide_device_t *) ide_devices[controller_nb][device_nb];

  assert (device != NULL);

  for (block_i = block_start ; 
       (block_i < (block_start + *inout_block_count))
         && (block_i < device->block_count) ;
       block_i++, buffer += device->blk_size)
    {
      result = _ide_io_polled_operation(device, IDE_WRITE, block_i,
                                        (k_ui16_t *) buffer);
      if(result < 0) 
        {
          return result;
        }
    }

  *inout_block_count = block_i - block_start;
  return ESUCCESS;
}

error_t ide_get_device_size(int controller_nb, int device_nb,
                            size_t *out_block_size, 
                            count_t *out_block_count)
{
  ide_device_t *device;

  device = (ide_device_t *) ide_devices[controller_nb][device_nb];

  assert (device != NULL);
  
  *out_block_size = device->blk_size;
  *out_block_count = device->block_count;

  return ESUCCESS;
}

error_t ide_init (void)
{
  _init_ide ();
}
/* ide.h - Access to ide devices.
   Copyright (C) 2005 Free Software Foundation, Inc.
   Written by Matthieu Lemerre.

   This file is part of the GNU Hurd.

   The GNU Hurd 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 2, or (at
   your option) any later version.

   The GNU Hurd 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 this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA. */

#ifndef __IDE_H__
#define __IDE_H__

#include <errno.h>


/* Initialize the IDE subsystem, detecting all the controllers and the
   devices.  */
error_t ide_init (void);

/* Read *INOUT_BLOCK_COUNT blocks from device DEVICE_NB on controller
   CONTROLLER_NB to BUFFER, starting at block BLOCK_START.  After the
   call, *INOUT_BLOCK_COUNT contains the number of blocks really
   read.  */
error_t ide_block_read(int controller_nb, int device_nb,
                       const char *buffer, unsigned int block_start,
                       unsigned int *inout_block_count);


/* Write *INOUT_BLOCK_COUNT blocks from BUFFER to device DEVICE_NB on
   controller CONTROLLER_NB, starting at block BLOCK_START.  After the
   call, *INOUT_BLOCK_COUNT contains the number of blocks really
   written.  */
error_t ide_block_write(int controller_nb, int device_nb,
                        const char *buffer, unsigned int block_start,
                        unsigned int *inout_block_count);


/* Return the block size (in *OUT_BLOCK_SIZE) and number of blocks (in
   OUT_BLOCK_COUNT) of the device DEVICE_NB on controller
   CONTROLLER_NB.  */
error_t ide_get_device_size(int controller_nb, int device_nb,
                            size_t *out_block_size, 
                            unsigned int *out_block_count);

#endif /* __IDE_H__ */
/* env.c - Functions needed by KOS' IDE driver.
   Copyright (C) 2005 Free Software Foundation, Inc.
   Written by Matthieu Lemerre.

   arch_loop_delay and udelay functions are from the KOS project;
   Copyright (C) 2000-2005, Thomas Petazzoni <address@hidden>
   Copyright (C) 2000-2005, David Decotigny <address@hidden>
   
   This file is part of the GNU Hurd.

   The GNU Hurd 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 2, or (at
   your option) any later version.

   The GNU Hurd 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 this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA. */

#include "env.h"
#include "_ide.h"

#include <l4/ipc.h>


static unsigned long loops_per_sec = 1 << 12;

void
arch_loop_delay(unsigned loop)
{
  
  int d0;                          
  __asm__ __volatile__("jmp 1f\n"  
                       ".align 16\n"
                       "1: jmp 2f\n"
                       ".align 16\n"
                       "2: decl %0\n"
                       "jns 2b\n"
                       :"=&a"(d0)     
                       :"0"(loop));
}


void
udelay(unsigned long usec)
{
  usec = (usec * loops_per_sec) / 1000000;
  arch_loop_delay (usec);
}


void
usleep (unsigned long usec)
{
  l4_sleep (l4_time_period (usec));
}



ide_device_t *ide_devices[IDE_MAX_CTRL][IDE_MAX_DEV_PER_CTRL] = {{NULL, NULL},
                                                                 {NULL, NULL}};

result_t _ide_register_disk(ide_device_t *device, int controller_number,
                            int device_number)
{
  debug ("[_ide_register_disk] called for controller %d device %d \n",
               controller_number, device_number);

  ide_devices[controller_number][device_number] = device;
  
  return ESUCCESS;
}
/* env.h - Definitions needed by KOS' IDE driver.
   Copyright (C) 2005 Free Software Foundation, Inc.
   Written by Matthieu Lemerre.

   This file is part of the GNU Hurd.

   The GNU Hurd 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 2, or (at
   your option) any later version.

   The GNU Hurd 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 this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA. */

#ifndef __ENV_H__
#define __ENV_H__

#include "../output.h"

#define _GNU_SOURCE 1
#include <stdlib.h>
#include <errno.h>
#include <sys/io.h>

typedef unsigned int count_t;

typedef unsigned long long k_ui64_t;
typedef long long k_si64_t;
typedef unsigned long k_ui32_t;
typedef long k_si32_t;
typedef unsigned short k_ui16_t;
typedef short k_si16_t;
typedef unsigned char k_ui8_t;
typedef char k_si8_t;

typedef error_t result_t;

#define ESUCCESS 0

/* Actively waits USECS microseconds.  */ 
void
udelay (unsigned long usecs);

/* Really sleep for DELAY microseconds.  */
void
usleep(unsigned long usec);


#define DEBUG_PRINT(format) \
  debug (format)

void
kmalloc (size_t size);

#define CONCEPTION_ASSERT(assertion) assert (assertion)

#define __dbg_printk debug

#define FAILED_VERBOSE(format) \
  panic (format)

#endif /* __ENV_H__ */
/* _ide.h - IDE internal declarations.

   Copyright (C) 2000-2005, Thomas Petazzoni <address@hidden>
   Copyright (C) 2005 Free Software Foundation, Inc.
   Modified by Matthieu Lemerre.

   arch_loop_delay and udelay functions are from the KOS project;
   Copyright (C) 2000-2005, David Decotigny <address@hidden>
   
   This file is part of the GNU Hurd.

   The GNU Hurd 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 2, or (at
   your option) any later version.

   The GNU Hurd 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 this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA. */

#ifndef ___IDE_H__
#define ___IDE_H__

/* Registers */
#define ATA_DATA                        0x00    /* (R/W) Data register */

#define ATA_ERROR                       0x01    /* (R) error register */

#define ATA_PRECOMP                     0x01    /* (W) precompensation */

#define ATA_SECTOR_COUNT                0x02    /* (W) sector count register */

#define ATA_SECTOR_NUMBER               0x03    /* (W) sector number register */

#define ATA_CYL_LSB                     0x04    /* (W) cylinder# LSB */

#define ATA_CYL_MSB                     0x05    /* (W) cylinder# MSB */

#define ATA_DRIVE                       0x06    /* (W) drive/head/lba */
#define         ATA_D_IBM               0xa0    /* bits that must be set */
#define         ATA_D_LBA               0x40    /* use LBA ? */
#define         ATA_D_MASTER            0x00    /* select master */
#define         ATA_D_SLAVE             0x10    /* select slave */


#define ATA_STATUS                      0x07    /* (R) status register */
#define         ATA_S_ERROR             0x01    /* error */
#define         ATA_S_INDEX             0x02    /* index */
#define         ATA_S_CORR              0x04    /* data corrected */
#define         ATA_S_DRQ               0x08    /* data request */
#define         ATA_S_DSC               0x10    /* drive Seek Completed */
#define         ATA_S_DWF               0x20    /* drive write fault */
#define         ATA_S_DRDY              0x40    /* drive ready */
#define         ATA_S_BSY               0x80    /* busy */

#define ATA_CMD                         0x07    /* (W) command register */
#define         ATA_C_ATA_IDENTIFY      0xec    /* get ATA params */
#define         ATA_C_ATAPI_IDENTIFY    0xa1    /* get ATAPI params*/
#define         ATA_C_READ              0x20    /* read command */
#define         ATA_C_WRITE             0x30    /* write command */
#define         ATA_C_READ_MULTI        0xc4    /* read multi command */
#define         ATA_C_WRITE_MULTI       0xc5    /* write multi command */
#define         ATA_C_SET_MULTI         0xc6    /* set multi size command */
#define         ATA_C_PACKET_CMD        0xa0    /* set multi size command */

#define ATA_ALTPORT                     0x206   /* (R) alternate
                                                   Status register */

#define ATA_DEVICE_CONTROL              0x206   /* (W) device control
                                                   register */
#define         ATA_A_nIEN              0x02    /* disable interrupts */
#define         ATA_A_RESET             0x04    /* RESET controller */
#define         ATA_A_4BIT              0x08    /* 4 head bits */

/* Magic numbers used to detect ATAPI devices */
#define ATAPI_MAGIC_LSB                 0x14
#define ATAPI_MAGIC_MSB                 0xeb

/* This structure describe the informations returned by the Identify
   Device command (imposed by the ATA standard) */
typedef struct ide_device_info
{
  k_ui16_t general_config_info;      /* 0 */
  k_ui16_t nb_logical_cylinders;     /* 1 */
  k_ui16_t reserved1;                /* 2 */
  k_ui16_t nb_logical_heads;         /* 3 */
  k_ui16_t unformatted_bytes_track;  /* 4 */
  k_ui16_t unformatted_bytes_sector; /* 5 */
  k_ui16_t nb_logical_sectors;       /* 6 */
  k_ui16_t vendor1[3];               /* 7-9 */
  k_ui8_t  serial_number[20];        /* 10-19 */
  k_ui16_t buffer_type;              /* 20 */
  k_ui16_t buffer_size;              /* 21 */
  k_ui16_t ecc_bytes;                /* 22 */
  k_ui8_t  firmware_revision[8];     /* 23-26 */
  k_ui8_t  model_number[40];         /* 27-46 */
  k_ui8_t  max_multisect;            /* 47 */
  k_ui8_t  vendor2;
  k_ui16_t dword_io;                 /* 48 */
  k_ui8_t  vendor3;                  /* 49 */
  k_ui8_t  capabilities;
  k_ui16_t reserved2;                /* 50 */
  k_ui8_t  vendor4;                  /* 51 */
  k_ui8_t  pio_trans_mode;
  k_ui8_t  vendor5;                  /* 52 */
  k_ui8_t  dma_trans_mode;
  k_ui16_t fields_valid;             /* 53 */
  k_ui16_t cur_logical_cylinders;    /* 54 */
  k_ui16_t cur_logical_heads;        /* 55 */
  k_ui16_t cur_logical_sectors;      /* 56 */
  k_ui16_t capacity1;                /* 57 */
  k_ui16_t capacity0;                /* 58 */
  k_ui8_t  multsect;                 /* 59 */
  k_ui8_t  multsect_valid;
  k_ui32_t lba_capacity;             /* 60-61 */
  k_ui16_t dma_1word;                /* 62 */
  k_ui16_t dma_multiword;            /* 63 */
  k_ui16_t pio_modes;                /* 64 */
  k_ui16_t min_mword_dma;            /* 65 */
  k_ui16_t recommended_mword_dma;    /* 66 */
  k_ui16_t min_pio_cycle_time;       /* 67 */
  k_ui16_t min_pio_cycle_time_iordy; /* 68 */
  k_ui16_t reserved3[11];            /* 69-79 */
  k_ui16_t major_version;            /* 80 */
  k_ui16_t minor_version;            /* 81 */
  k_ui16_t command_sets1;            /* 82 */
  k_ui16_t command_sets2;            /* 83 dixit lk : b14 (smart enabled) */
  k_ui16_t reserved4[4];             /* 84-87 */
  k_ui16_t dma_ultra;                /* 88 dixit lk */
  k_ui16_t reserved5[37];            /* 89-125 */
  k_ui16_t last_lun;                 /* 126 */
  k_ui16_t reserved6;                /* 127 */
  k_ui16_t security;                 /* 128 */
  k_ui16_t reserved7[127];
} __attribute__((packed)) ide_device_info_t ;


#define IDE_MAX_CTRL             2
#define IDE_MAX_DEV_PER_CTRL     2
#define IDE_PRIMARY_CONTROLLER   0
#define IDE_SECONDARY_CONTROLLER 1

#define IDE_MASTER 0
#define IDE_SLAVE  1
#define IDE_READ   0
#define IDE_WRITE  1

typedef enum { IDE_DEV_NOT_PRESENT=0x42, 
               IDE_DEV_ATA, IDE_DEV_ATAPI } ide_device_type_t;

struct ide_controller;
typedef struct ide_device
{
  uint id;
  dev_t devfs_id;

  ide_device_type_t type;

  k_ui16_t cylinder_nb;
  k_ui16_t head_nb;
  k_ui16_t sector_per_track;
  count_t  block_count;
  size_t   blk_size;

  char model_name[41];

  struct ide_controller *ctrl;
#define HARDDISK_LBA_CAPABLE 0x1
  int flags;

} ide_device_t;

typedef struct ide_controller
{
  uint id;
  k_ui16_t ioaddr;
  k_ui16_t irq;

  enum { IDE_CTRL_NOT_PRESENT, IDE_CTRL_READY } state;

  struct ide_device devices[IDE_MAX_DEV_PER_CTRL];
} ide_controller_t;

/* _ide.c */

/**
 * Initialize IDE controllers and devices
 *
 * @return ESUCCESS or error code
 */
result_t _init_ide(void);

/**
 * Perform an I/O operation in polled mode.
 *
 * @param dev The device
 *
 * @param op_type Type of operation to be performed (IDE_READ or IDE_WRITE)
 *
 * @param block_start The sector number at which the operation starts
 *
 * @param buffer The buffer to be used for the operation (as input or output)
 *
 * @todo Use IRQ
 * @todo Implement multiple sector operations
 *
 * @result ESUCCESS or error code
 */
result_t _ide_io_polled_operation(ide_device_t *dev, int op_type,
                                  count_t block_start, k_ui16_t *buffer);

#ifdef KOS
/* _ide_karm.c */
result_t _ide_register_disk(char *name, ide_device_t *device);
#else
result_t _ide_register_disk(ide_device_t *device, int controller_number,
                            int device_number);
#endif /* KOS */

#endif /* ___IDE_H__ */



/*
 * Copyright (C) 2000, Thomas Petazzoni <address@hidden>
 * http://kos.enix.org
 *
 * ATA driver
 * with the help of Soren Schmidt, <address@hidden>
 *
 * Copyright (C) 2005 Free Software Foundation, Inc.
 * Modified by Matthieu Lemerre <address@hidden>
 * 
 * @(#) $Id: _ide.c,v 1.17 2003/08/20 22:12:21 d2 Exp $
 */


#if KOS
  #include <kos/asm.h>
  #include <scheduler/scheduler.h>
  #include <idt/irq.h>
  #include <ide/_ide.h>
  #include <lib/list/liblist.h>
  #include <kos/spinlock.h>
  #include <lib/std/string.h>
  #include <kmem/kmem.h>
  #include <debug/debug.h>
#endif


#include "env.h"
#include "_ide.h"

/**
 * Description of all IDE controllers and their devices.
 */
static ide_controller_t controllers[IDE_MAX_CTRL] =
{
  { .id      = IDE_PRIMARY_CONTROLLER, 
    .ioaddr  = 0x1F0, 
    .irq     = 14, 
    .state   = IDE_CTRL_NOT_PRESENT, 
    .devices = {
      [IDE_MASTER] = { 
        .id = IDE_MASTER,
        .type = IDE_DEV_NOT_PRESENT,
        .ctrl = & controllers[IDE_PRIMARY_CONTROLLER]
      },
      [IDE_SLAVE] = { 
        .id = IDE_SLAVE,
        .type = IDE_DEV_NOT_PRESENT,
        .ctrl = & controllers[IDE_PRIMARY_CONTROLLER]
      }
    }
  },
  { .id      = IDE_SECONDARY_CONTROLLER, 
    .ioaddr  = 0x170, 
    .irq     = 15, 
    .state   = IDE_CTRL_NOT_PRESENT, 
    .devices = {
      [IDE_MASTER] = { 
        .id = IDE_MASTER,
        .type = IDE_DEV_NOT_PRESENT,
        .ctrl = & controllers[IDE_SECONDARY_CONTROLLER]
      },
      [IDE_SLAVE] = { 
        .id = IDE_SLAVE,
        .type = IDE_DEV_NOT_PRESENT,
        .ctrl = & controllers[IDE_SECONDARY_CONTROLLER]
      }
    }
  }
};

/**
` * Initialize a controller :
 *  -# Make sure controller is present
 *  -# See which devices are present on the controller
 *
 * @param ctrl The controller to initialize
 *
 * @result 0 on success, error code on failure
 */ 
static result_t _ide_init_controller(ide_controller_t *ctrl)
{
  int status0, status1;
  int mask = 0;
  int timeout;

  /* Get status of controllers */
  outb(ATA_D_IBM | ATA_D_MASTER, ctrl->ioaddr + ATA_DRIVE);
  udelay(1);
  status0 = inb(ctrl->ioaddr + ATA_STATUS);

  outb(ATA_D_IBM | ATA_D_SLAVE, ctrl->ioaddr + ATA_DRIVE);
  udelay(1);
  status1 = inb(ctrl->ioaddr + ATA_STATUS);
  
  if((status0 & 0xf8) != 0xf8)
    mask |= 0x01;
  if((status1 & 0xf8) != 0xf8)
    mask |= 0x02;

  /* If both BSY bit are set => leave */
  if((status0 & ATA_S_BSY) && (status1 & ATA_S_BSY))
    return ESUCCESS;

  /* no device present */
  if(!mask)
    return ESUCCESS;
  
  /* select the master */
  outb(ATA_D_IBM | ATA_D_MASTER, ctrl->ioaddr + ATA_DRIVE);
  usleep(1);
  /* send reset */
  outb(ATA_A_nIEN | ATA_A_RESET, ctrl->ioaddr + ATA_DEVICE_CONTROL);
  udelay(1);
  outb(ATA_A_nIEN, ctrl->ioaddr + ATA_DEVICE_CONTROL);
  udelay(1);
  inb(ctrl->ioaddr + ATA_ERROR);
  outb(ATA_A_4BIT, ctrl->ioaddr + ATA_DEVICE_CONTROL);
  udelay(1);

  /* wait busy */
  for(timeout = 0; timeout < 300000; timeout++)
    {
      /* select master and get status register */
      outb(ATA_D_IBM | ATA_D_MASTER, ctrl->ioaddr + ATA_DRIVE);
      udelay(1);
      status0 = inb(ctrl->ioaddr + ATA_STATUS);
      
      /* select slave and get status register */
      outb(ATA_D_IBM | ATA_D_SLAVE, ctrl->ioaddr + ATA_DRIVE);
      udelay(1);
      status1 = inb(ctrl->ioaddr + ATA_STATUS);

      if(mask == 0x01)
        if(!(status0 & ATA_S_BSY))
          break;
      if(mask == 0x02)
        if(!(status1 & ATA_S_BSY))
          break;
      if(mask == 0x03)
        if(!(status0 & ATA_S_BSY) && !(status1 & ATA_S_BSY))
          break;
      udelay(1);
    }

  if(status0 & ATA_S_BSY)
    mask &= ~0x01;
  if(status1 & ATA_S_BSY)
    mask &= ~0x02;
  
  /* no device */
  if(!mask)
    return ESUCCESS;

  /* there's at least one device, initialize state */
  ctrl->state = IDE_CTRL_READY;

  /* Detect devices on the master */
  outb(ATA_D_IBM | ATA_D_MASTER, ctrl->ioaddr + ATA_DRIVE);
  udelay(1);
  
  /* ATAPI cdrom ? */
  if(inb(ctrl->ioaddr + ATA_CYL_LSB) == ATAPI_MAGIC_LSB &&
     inb(ctrl->ioaddr + ATA_CYL_MSB) == ATAPI_MAGIC_MSB)
    {
      ctrl->devices[IDE_MASTER].type = IDE_DEV_ATAPI;
    }
  
  /* Detect devices on the slave */
  outb(ATA_D_IBM | ATA_D_SLAVE, ctrl->ioaddr + ATA_DRIVE);
  udelay(1);
  
  /* ATAPI cdrom ? */
  if(inb(ctrl->ioaddr + ATA_CYL_LSB) == ATAPI_MAGIC_LSB &&
     inb(ctrl->ioaddr + ATA_CYL_MSB) == ATAPI_MAGIC_MSB)
    {
      ctrl->devices[IDE_SLAVE].type = IDE_DEV_ATAPI;
    }

  /* Wasn't an ATAPI device on master, but there is a device -> hard
     disk */
  if((status0 != 0x00) && 
     (ctrl->devices[IDE_MASTER].type == IDE_DEV_NOT_PRESENT))
    {
      outb(ATA_D_IBM | ATA_D_MASTER, ctrl->ioaddr + ATA_DRIVE);
      usleep(1);
      outb(0x58, ctrl->ioaddr + ATA_ERROR);
      outb(0xa5, ctrl->ioaddr + ATA_CYL_LSB);
      if(inb(ctrl->ioaddr + ATA_ERROR) != 0x58 &&
         inb(ctrl->ioaddr + ATA_CYL_LSB) == 0xa5)
        {
          ctrl->devices[IDE_MASTER].type = IDE_DEV_ATA;
        }
    }

  /* Wasn't an ATAPI device on slave, but there is a device -> hard
     disk */  
  if((status1 != 0x00) && 
     (ctrl->devices[IDE_SLAVE].type == IDE_DEV_NOT_PRESENT))
    {
      outb(ATA_D_IBM | ATA_D_SLAVE, ctrl->ioaddr + ATA_DRIVE);
      usleep(1);
      outb(0x58, ctrl->ioaddr + ATA_ERROR);
      outb(0xa5, ctrl->ioaddr + ATA_CYL_LSB);
      if(inb(ctrl->ioaddr + ATA_ERROR) != 0x58 &&
         inb(ctrl->ioaddr + ATA_CYL_LSB) == 0xa5)
        {
          ctrl->devices[IDE_MASTER].type = IDE_DEV_ATA;
        }
    }

  return ESUCCESS;
}

/**
 * Convert an IDE string (as returned by the controller) to a regular
 * ASCII string.
 *
 * @param dst The address where to store the resulting ASCII string
 *
 * @param src The source IDE string
 *
 * @param len The length of the conversion (destination string must be
 *            long enough)
 */
static void _ide_convert_to_ascii(char *dst, char *src, int len)
{
  int i;
  
  for(i = 0; i < len; i=i+2)
    {
      dst[i] = (src[i+1] & 0xff);
      dst[i+1] = (src[i] & 0xff);
    }

  dst[len] = '\0';
  for(i = len; i > 0; i--)
    {
      if(dst[i] == 0)
        continue;
      else if(dst[i] == ' ')
        dst[i] = '\0';
      else
        break;
    }
}

/** 
 * Get device informations for ATA devices. This function sends the
 * "Identify drive" command, and wait for the result in polled mode.
 *
 * @param dev The device for which we want to get informations
 *
 * @result ESUCCESS or error code
 */
static result_t _ide_get_device_info(ide_device_t *dev)
{ 
  int timeout, status=0;
  k_ui16_t buffer[256];
  int ioaddr = dev->ctrl->ioaddr;
  ide_device_info_t *dinfo;
  int i;

  /* Disable interrupts, set device, and send IDENTIFY command */
  outb(ATA_A_nIEN | ATA_A_4BIT, ioaddr + ATA_DEVICE_CONTROL);
  udelay(1);
  outb(ATA_D_IBM | (dev->id == IDE_MASTER) ? ATA_D_MASTER : ATA_D_SLAVE, 
       ioaddr + ATA_DRIVE);
  outb(ATA_C_ATA_IDENTIFY, ioaddr + ATA_CMD);

  /* Wait for command completion */
  for(timeout = 0; timeout < 30000; timeout++)
    {
      status = inb(ioaddr + ATA_STATUS);
      if(!(status & ATA_S_BSY))
        break;
      
      udelay(1);
    }

  /* Check for DRQ to be set */
  if(!(status & ATA_S_DRQ))
    {
      __dbg_printk("[get_drv_infos] DRQ wasn't set after IDENTIFY\n");
      return -1;
    }

  /* Read data */
  for(i = 0; i < 256; i++)
    buffer[i] = inw(ioaddr);

  dinfo = (ide_device_info_t *) buffer;

  dev->cylinder_nb      = dinfo->nb_logical_cylinders;
  dev->head_nb          = dinfo->nb_logical_heads;
  dev->sector_per_track = dinfo->nb_logical_sectors;
  dev->blk_size         = 512;

  if(dev->sector_per_track == 0 || dev->head_nb == 0 ||
     dev->cylinder_nb == 0)
    {
      return -ENOENT;
    }
  
  dev->flags = 0;

  /* Does this device really support LBA */
  if(dinfo->capabilities & (1<<1)     /* LBA bit must be set */
     && dinfo->major_version          /* major version must be non 0 */
     && (dinfo->fields_valid & 1)     /* lba fields must be valid */
     && (dinfo->lba_capacity))        /* lba capacity must be non 0 */
    dev->flags |= HARDDISK_LBA_CAPABLE;

  /* Find real capacity */
  if(dev->head_nb == 16 &&
     dev->sector_per_track == 63 &&
     dev->cylinder_nb == 16383)
    {
      if(dev->flags & HARDDISK_LBA_CAPABLE)
        dev->block_count = dinfo->lba_capacity;
      else
        FAILED_VERBOSE("Large hd without LBA ...\n");
    }
  else
    dev->block_count = 
      dev->cylinder_nb * 
      dev->sector_per_track *
      dev->head_nb;

  /* Convert different strings */
  _ide_convert_to_ascii(dev->model_name, dinfo->model_number, 40);

  return ESUCCESS;
}

/**
 * Register IDE devices
 *  -# Get device informations
 *  -# Register them to the devfs
 *
 * @param ctrl The controller in which the devices to be registered
 * are located
 *
 * @result ESUCCESS on success, an error code on failure
 */
static result_t _ide_register_devices(ide_controller_t *ctrl)
{
  uint i;

  if(ctrl->state == IDE_CTRL_NOT_PRESENT)
    return ESUCCESS;
  
  for (i = 0; i < IDE_MAX_DEV_PER_CTRL; i++)
    {
      #if KOS
      char name[16];
      #endif
      
      ide_device_t *dev;
      result_t result;

      dev = & ctrl->devices[i];
      
      if (dev->type == IDE_DEV_NOT_PRESENT)
        {
          __dbg_printk("[ide] Device %d on ide%d not present\n", i, ctrl->id);
          continue;
        }
      else if (dev->type == IDE_DEV_ATA)
        {
          #if KOS
          snprintf(name, sizeof(name), "ide%dhd%d", ctrl->id, i);
          #endif
          
          result = _ide_get_device_info(dev);
          if (result < 0)
            {
              dev->type = IDE_DEV_NOT_PRESENT;
              continue;
            }

          __dbg_printk("[ide] Detected Disk device, C/H/S=%d/%d/%d, 
size=%dMB\n",
                       dev->cylinder_nb, dev->head_nb,
                       dev->sector_per_track, dev->block_count >> 11);

          #if KOS
          result = _ide_register_disk(name, dev);
          #else
          result = _ide_register_disk(dev, ctrl->id, i);
          #endif
          if (result < 0)
            {
              continue;
            }
        }
      else if (ctrl->devices[i].type == IDE_DEV_ATAPI)
        {
          #if KOS
          snprintf(name, sizeof(name), "ide%dcd%d", ctrl->id, i);
          #endif
          
          __dbg_printk("[ide] Detected CDROM device\n");
        }
    }
  
  return ESUCCESS;
}

result_t _init_ide(void)
{
  uint i;
  
  for (i = 0; i < IDE_MAX_CTRL; i++)
    {
      ide_controller_t *ctrl;
      result_t result;

      ctrl = & controllers[i];
      result = _ide_init_controller(ctrl);
      if(result < 0)
        {
          return result;
        }

      result = _ide_register_devices(ctrl);
      if(result < 0)
        {
          return result;
        }
    }

  return ESUCCESS;
}


result_t _ide_io_polled_operation(ide_device_t *dev, int op_type, 
                                  count_t block_start, k_ui16_t *buffer)
{
  int timeout, status = 0;
  int i;
  k_ui8_t cyl_lo, cyl_hi, sect, head;
  ide_controller_t *ctrl;
  
  ctrl = dev->ctrl;

  if(op_type != IDE_READ && op_type != IDE_WRITE)
    {
      DEBUG_PRINT("type invalid\n");
      return -EINVAL;
    }
  
  if (ctrl->state == IDE_CTRL_NOT_PRESENT)
    {
      return -ENXIO;
    }

  if (ctrl->state != IDE_CTRL_READY)
    {
      return -EBUSY;
    }

  /* Does harddisk support LBA ? */
  if(dev->flags & HARDDISK_LBA_CAPABLE)
    {
      sect   = (block_start & 0xff);
      cyl_lo = (block_start >> 8) & 0xff;
      cyl_hi = (block_start >> 16) & 0xff;
      head   = ((block_start >> 24) & 0x7) | 0x40;
    }
  else
    {
      int cylinder = block_start / 
        (dev->head_nb * dev->sector_per_track);
      int temp = block_start %
        (dev->head_nb * dev->sector_per_track);
      cyl_lo = cylinder & 0xff;
      cyl_hi = (cylinder >> 8) & 0xff;
      head   = temp / dev->sector_per_track;
      sect   = (temp % dev->sector_per_track) + 1;
    } 

  /* I don't know if we really need this, but without this, the Error
     bit of the status register is set, even before sending the
     command ! */
  outb(ATA_D_IBM | ATA_D_MASTER, ctrl->ioaddr + ATA_DRIVE);
  udelay(100);
  outb(ATA_A_nIEN | ATA_A_RESET, ctrl->ioaddr + ATA_DEVICE_CONTROL);
  udelay(100);
  outb(ATA_A_nIEN, ctrl->ioaddr + ATA_DEVICE_CONTROL);
  inb(ctrl->ioaddr + ATA_ERROR);

  /* Waiting for controller to be ready (BSY=0) */
  for(timeout = 0; timeout < 30000; timeout++)
    {
      status = inb(ctrl->ioaddr + ATA_STATUS);
      if(!(status & ATA_S_BSY))
        break;
      
      udelay(100);
    }

  if(timeout == 30000)
    {
      DEBUG_PRINT("[ide] timeout 1\n");
      return -EIO;
    }

  /* Check for ERR bit to be cleared */
  if(inb(ctrl->ioaddr + ATA_STATUS) & ATA_S_ERROR)
    {
      DEBUG_PRINT("err bit set\n");
      return -EIO;
    }

  /* Write to registers */
  outb(ATA_A_nIEN | ATA_A_4BIT, ctrl->ioaddr + ATA_DEVICE_CONTROL);
  outb(1, ctrl->ioaddr + 1);
  outb(0, ctrl->ioaddr + ATA_PRECOMP);
  outb(1, ctrl->ioaddr + ATA_SECTOR_COUNT);
  outb(sect, ctrl->ioaddr + ATA_SECTOR_NUMBER);
  outb(cyl_lo, ctrl->ioaddr + ATA_CYL_LSB);
  outb(cyl_hi, ctrl->ioaddr + ATA_CYL_MSB);
  outb(ATA_D_IBM
       | ((dev->id == IDE_MASTER) ? ATA_D_MASTER : ATA_D_SLAVE)
       | head, 
       ctrl->ioaddr + ATA_DRIVE);

  /* ATA-4 says to wait 400nsec here, but it doesn't change anything ! */
  udelay(100);

  /* For the moment, we don't care about the operation type => always
     read */
  if(op_type == IDE_READ)
    {
      outb(ATA_C_READ, ctrl->ioaddr + ATA_CMD);
    }
  else if(op_type == IDE_WRITE)
    {
      outb(ATA_C_WRITE, ctrl->ioaddr + ATA_CMD);
    }

  /* Wait for controller to be ready (BSY=0) */
  for(timeout = 0; timeout < 30000; timeout++)
    {
      status = inb(ctrl->ioaddr + ATA_STATUS);
      if(!(status & ATA_S_BSY))
        break;
      
      udelay(100);
    }

  if(timeout >= 30000)
    {
      DEBUG_PRINT("timeout 2\n");
      return -EIO;
    }
  
  /* Check for ERR to be cleared */
  if(inb(ctrl->ioaddr + ATA_STATUS) & ATA_S_ERROR)
    {
      DEBUG_PRINT("err bit set\n");
      return -EIO;
    }

  /* Check for DRQ to be set */
  if(!(inb(ctrl->ioaddr + ATA_STATUS) & ATA_S_DRQ))
    {
      DEBUG_PRINT("drq not set\n");
      return -EIO;
    }

  /* Read the data */
  if(op_type == IDE_READ)
    {
      for(i = 0; i < 256; i++)
        buffer[i] = inw(ctrl->ioaddr);
    }
  else if(op_type == IDE_WRITE)
    {
      for (i = 0; i < 256; i++)
        outw(buffer[i], ctrl->ioaddr);
    }

  return ESUCCESS;
}


reply via email to

[Prev in Thread] Current Thread [Next in Thread]