Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
P
Pintos_forked_resit
Manage
Activity
Members
Plan
Wiki
Code
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Deploy
Releases
Package registry
Model registry
Operate
Terraform modules
Analyze
Contributor analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
s2-alsaloumi
Pintos_forked_resit
Commits
548ee373
Commit
548ee373
authored
1 year ago
by
h2-addad
Browse files
Options
Downloads
Patches
Plain Diff
Update
parent
8966c21e
No related branches found
No related tags found
No related merge requests found
Changes
1
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
src/devices/ide.c
+527
-0
527 additions, 0 deletions
src/devices/ide.c
with
527 additions
and
0 deletions
src/devices/ide.c
0 → 100644
+
527
−
0
View file @
548ee373
#include
"devices/ide.h"
#include
<ctype.h>
#include
<debug.h>
#include
<stdbool.h>
#include
<stdio.h>
#include
"devices/block.h"
#include
"devices/partition.h"
#include
"devices/timer.h"
#include
"threads/io.h"
#include
"threads/interrupt.h"
#include
"threads/synch.h"
/* The code in this file is an interface to an ATA (IDE)
controller. It attempts to comply to [ATA-3]. */
/* ATA command block port addresses. */
#define reg_data(CHANNEL) ((CHANNEL)->reg_base + 0)
/* Data. */
#define reg_error(CHANNEL) ((CHANNEL)->reg_base + 1)
/* Error. */
#define reg_nsect(CHANNEL) ((CHANNEL)->reg_base + 2)
/* Sector Count. */
#define reg_lbal(CHANNEL) ((CHANNEL)->reg_base + 3)
/* LBA 0:7. */
#define reg_lbam(CHANNEL) ((CHANNEL)->reg_base + 4)
/* LBA 15:8. */
#define reg_lbah(CHANNEL) ((CHANNEL)->reg_base + 5)
/* LBA 23:16. */
#define reg_device(CHANNEL) ((CHANNEL)->reg_base + 6)
/* Device/LBA 27:24. */
#define reg_status(CHANNEL) ((CHANNEL)->reg_base + 7)
/* Status (r/o). */
#define reg_command(CHANNEL) reg_status (CHANNEL)
/* Command (w/o). */
/* ATA control block port addresses.
(If we supported non-legacy ATA controllers this would not be
flexible enough, but it's fine for what we do.) */
#define reg_ctl(CHANNEL) ((CHANNEL)->reg_base + 0x206)
/* Control (w/o). */
#define reg_alt_status(CHANNEL) reg_ctl (CHANNEL)
/* Alt Status (r/o). */
/* Alternate Status Register bits. */
#define STA_BSY 0x80
/* Busy. */
#define STA_DRDY 0x40
/* Device Ready. */
#define STA_DRQ 0x08
/* Data Request. */
/* Control Register bits. */
#define CTL_SRST 0x04
/* Software Reset. */
/* Device Register bits. */
#define DEV_MBS 0xa0
/* Must be set. */
#define DEV_LBA 0x40
/* Linear based addressing. */
#define DEV_DEV 0x10
/* Select device: 0=master, 1=slave. */
/* Commands.
Many more are defined but this is the small subset that we
use. */
#define CMD_IDENTIFY_DEVICE 0xec
/* IDENTIFY DEVICE. */
#define CMD_READ_SECTOR_RETRY 0x20
/* READ SECTOR with retries. */
#define CMD_WRITE_SECTOR_RETRY 0x30
/* WRITE SECTOR with retries. */
/* An ATA device. */
struct
ata_disk
{
char
name
[
8
];
/* Name, e.g. "hda". */
struct
channel
*
channel
;
/* Channel that disk is attached to. */
int
dev_no
;
/* Device 0 or 1 for master or slave. */
bool
is_ata
;
/* Is device an ATA disk? */
};
/* An ATA channel (aka controller).
Each channel can control up to two disks. */
struct
channel
{
char
name
[
8
];
/* Name, e.g. "ide0". */
uint16_t
reg_base
;
/* Base I/O port. */
uint8_t
irq
;
/* Interrupt in use. */
struct
lock
lock
;
/* Must acquire to access the controller. */
bool
expecting_interrupt
;
/* True if an interrupt is expected, false if
any interrupt would be spurious. */
struct
semaphore
completion_wait
;
/* Up'd by interrupt handler. */
struct
ata_disk
devices
[
2
];
/* The devices on this channel. */
};
/* We support the two "legacy" ATA channels found in a standard PC. */
#define CHANNEL_CNT 2
static
struct
channel
channels
[
CHANNEL_CNT
];
static
struct
block_operations
ide_operations
;
static
void
reset_channel
(
struct
channel
*
);
static
bool
check_device_type
(
struct
ata_disk
*
);
static
void
identify_ata_device
(
struct
ata_disk
*
);
static
void
select_sector
(
struct
ata_disk
*
,
block_sector_t
);
static
void
issue_pio_command
(
struct
channel
*
,
uint8_t
command
);
static
void
input_sector
(
struct
channel
*
,
void
*
);
static
void
output_sector
(
struct
channel
*
,
const
void
*
);
static
void
wait_until_idle
(
const
struct
ata_disk
*
);
static
bool
wait_while_busy
(
const
struct
ata_disk
*
);
static
void
select_device
(
const
struct
ata_disk
*
);
static
void
select_device_wait
(
const
struct
ata_disk
*
);
static
void
interrupt_handler
(
struct
intr_frame
*
);
/* Initialize the disk subsystem and detect disks. */
void
ide_init
(
void
)
{
size_t
chan_no
;
for
(
chan_no
=
0
;
chan_no
<
CHANNEL_CNT
;
chan_no
++
)
{
struct
channel
*
c
=
&
channels
[
chan_no
];
int
dev_no
;
/* Initialize channel. */
snprintf
(
c
->
name
,
sizeof
c
->
name
,
"ide%zu"
,
chan_no
);
switch
(
chan_no
)
{
case
0
:
c
->
reg_base
=
0x1f0
;
c
->
irq
=
14
+
0x20
;
break
;
case
1
:
c
->
reg_base
=
0x170
;
c
->
irq
=
15
+
0x20
;
break
;
default:
NOT_REACHED
();
}
lock_init
(
&
c
->
lock
);
c
->
expecting_interrupt
=
false
;
sema_init
(
&
c
->
completion_wait
,
0
);
/* Initialize devices. */
for
(
dev_no
=
0
;
dev_no
<
2
;
dev_no
++
)
{
struct
ata_disk
*
d
=
&
c
->
devices
[
dev_no
];
snprintf
(
d
->
name
,
sizeof
d
->
name
,
"hd%c"
,
'a'
+
chan_no
*
2
+
dev_no
);
d
->
channel
=
c
;
d
->
dev_no
=
dev_no
;
d
->
is_ata
=
false
;
}
/* Register interrupt handler. */
intr_register_ext
(
c
->
irq
,
interrupt_handler
,
c
->
name
);
/* Reset hardware. */
reset_channel
(
c
);
/* Distinguish ATA hard disks from other devices. */
if
(
check_device_type
(
&
c
->
devices
[
0
]))
check_device_type
(
&
c
->
devices
[
1
]);
/* Read hard disk identity information. */
for
(
dev_no
=
0
;
dev_no
<
2
;
dev_no
++
)
if
(
c
->
devices
[
dev_no
].
is_ata
)
identify_ata_device
(
&
c
->
devices
[
dev_no
]);
}
}
/* Disk detection and identification. */
static
char
*
descramble_ata_string
(
char
*
,
int
size
);
/* Resets an ATA channel and waits for any devices present on it
to finish the reset. */
static
void
reset_channel
(
struct
channel
*
c
)
{
bool
present
[
2
];
int
dev_no
;
/* The ATA reset sequence depends on which devices are present,
so we start by detecting device presence. */
for
(
dev_no
=
0
;
dev_no
<
2
;
dev_no
++
)
{
struct
ata_disk
*
d
=
&
c
->
devices
[
dev_no
];
select_device
(
d
);
outb
(
reg_nsect
(
c
),
0x55
);
outb
(
reg_lbal
(
c
),
0xaa
);
outb
(
reg_nsect
(
c
),
0xaa
);
outb
(
reg_lbal
(
c
),
0x55
);
outb
(
reg_nsect
(
c
),
0x55
);
outb
(
reg_lbal
(
c
),
0xaa
);
present
[
dev_no
]
=
(
inb
(
reg_nsect
(
c
))
==
0x55
&&
inb
(
reg_lbal
(
c
))
==
0xaa
);
}
/* Issue soft reset sequence, which selects device 0 as a side effect.
Also enable interrupts. */
outb
(
reg_ctl
(
c
),
0
);
timer_usleep
(
10
);
outb
(
reg_ctl
(
c
),
CTL_SRST
);
timer_usleep
(
10
);
outb
(
reg_ctl
(
c
),
0
);
timer_msleep
(
150
);
/* Wait for device 0 to clear BSY. */
if
(
present
[
0
])
{
select_device
(
&
c
->
devices
[
0
]);
wait_while_busy
(
&
c
->
devices
[
0
]);
}
/* Wait for device 1 to clear BSY. */
if
(
present
[
1
])
{
int
i
;
select_device
(
&
c
->
devices
[
1
]);
for
(
i
=
0
;
i
<
3000
;
i
++
)
{
if
(
inb
(
reg_nsect
(
c
))
==
1
&&
inb
(
reg_lbal
(
c
))
==
1
)
break
;
timer_msleep
(
10
);
}
wait_while_busy
(
&
c
->
devices
[
1
]);
}
}
/* Checks whether device D is an ATA disk and sets D's is_ata
member appropriately. If D is device 0 (master), returns true
if it's possible that a slave (device 1) exists on this
channel. If D is device 1 (slave), the return value is not
meaningful. */
static
bool
check_device_type
(
struct
ata_disk
*
d
)
{
struct
channel
*
c
=
d
->
channel
;
uint8_t
error
,
lbam
,
lbah
,
status
;
select_device
(
d
);
error
=
inb
(
reg_error
(
c
));
lbam
=
inb
(
reg_lbam
(
c
));
lbah
=
inb
(
reg_lbah
(
c
));
status
=
inb
(
reg_status
(
c
));
if
((
error
!=
1
&&
(
error
!=
0x81
||
d
->
dev_no
==
1
))
||
(
status
&
STA_DRDY
)
==
0
||
(
status
&
STA_BSY
)
!=
0
)
{
d
->
is_ata
=
false
;
return
error
!=
0x81
;
}
else
{
d
->
is_ata
=
(
lbam
==
0
&&
lbah
==
0
)
||
(
lbam
==
0x3c
&&
lbah
==
0xc3
);
return
true
;
}
}
/* Sends an IDENTIFY DEVICE command to disk D and reads the
response. Registers the disk with the block device
layer. */
static
void
identify_ata_device
(
struct
ata_disk
*
d
)
{
struct
channel
*
c
=
d
->
channel
;
char
id
[
BLOCK_SECTOR_SIZE
];
block_sector_t
capacity
;
char
*
model
,
*
serial
;
char
extra_info
[
128
];
struct
block
*
block
;
ASSERT
(
d
->
is_ata
);
/* Send the IDENTIFY DEVICE command, wait for an interrupt
indicating the device's response is ready, and read the data
into our buffer. */
select_device_wait
(
d
);
issue_pio_command
(
c
,
CMD_IDENTIFY_DEVICE
);
sema_down
(
&
c
->
completion_wait
);
if
(
!
wait_while_busy
(
d
))
{
d
->
is_ata
=
false
;
return
;
}
input_sector
(
c
,
id
);
/* Calculate capacity.
Read model name and serial number. */
capacity
=
*
(
uint32_t
*
)
&
id
[
60
*
2
];
model
=
descramble_ata_string
(
&
id
[
10
*
2
],
20
);
serial
=
descramble_ata_string
(
&
id
[
27
*
2
],
40
);
snprintf
(
extra_info
,
sizeof
extra_info
,
"model
\"
%s
\"
, serial
\"
%s
\"
"
,
model
,
serial
);
/* Disable access to IDE disks over 1 GB, which are likely
physical IDE disks rather than virtual ones. If we don't
allow access to those, we're less likely to scribble on
someone's important data. You can disable this check by
hand if you really want to do so. */
if
(
capacity
>=
1024
*
1024
*
1024
/
BLOCK_SECTOR_SIZE
)
{
printf
(
"%s: ignoring "
,
d
->
name
);
print_human_readable_size
(
capacity
*
512
);
printf
(
"disk for safety
\n
"
);
d
->
is_ata
=
false
;
return
;
}
/* Register. */
block
=
block_register
(
d
->
name
,
BLOCK_RAW
,
extra_info
,
capacity
,
&
ide_operations
,
d
);
partition_scan
(
block
);
}
/* Translates STRING, which consists of SIZE bytes in a funky
format, into a null-terminated string in-place. Drops
trailing whitespace and null bytes. Returns STRING. */
static
char
*
descramble_ata_string
(
char
*
string
,
int
size
)
{
int
i
;
/* Swap all pairs of bytes. */
for
(
i
=
0
;
i
+
1
<
size
;
i
+=
2
)
{
char
tmp
=
string
[
i
];
string
[
i
]
=
string
[
i
+
1
];
string
[
i
+
1
]
=
tmp
;
}
/* Find the last non-white, non-null character. */
for
(
size
--
;
size
>
0
;
size
--
)
{
int
c
=
string
[
size
-
1
];
if
(
c
!=
'\0'
&&
!
isspace
(
c
))
break
;
}
string
[
size
]
=
'\0'
;
return
string
;
}
/* Reads sector SEC_NO from disk D into BUFFER, which must have
room for BLOCK_SECTOR_SIZE bytes.
Internally synchronizes accesses to disks, so external
per-disk locking is unneeded. */
static
void
ide_read
(
void
*
d_
,
block_sector_t
sec_no
,
void
*
buffer
)
{
struct
ata_disk
*
d
=
d_
;
struct
channel
*
c
=
d
->
channel
;
lock_acquire
(
&
c
->
lock
);
select_sector
(
d
,
sec_no
);
issue_pio_command
(
c
,
CMD_READ_SECTOR_RETRY
);
sema_down
(
&
c
->
completion_wait
);
if
(
!
wait_while_busy
(
d
))
PANIC
(
"%s: disk read failed, sector=%"
PRDSNu
,
d
->
name
,
sec_no
);
input_sector
(
c
,
buffer
);
lock_release
(
&
c
->
lock
);
}
/* Write sector SEC_NO to disk D from BUFFER, which must contain
BLOCK_SECTOR_SIZE bytes. Returns after the disk has
acknowledged receiving the data.
Internally synchronizes accesses to disks, so external
per-disk locking is unneeded. */
static
void
ide_write
(
void
*
d_
,
block_sector_t
sec_no
,
const
void
*
buffer
)
{
struct
ata_disk
*
d
=
d_
;
struct
channel
*
c
=
d
->
channel
;
lock_acquire
(
&
c
->
lock
);
select_sector
(
d
,
sec_no
);
issue_pio_command
(
c
,
CMD_WRITE_SECTOR_RETRY
);
if
(
!
wait_while_busy
(
d
))
PANIC
(
"%s: disk write failed, sector=%"
PRDSNu
,
d
->
name
,
sec_no
);
output_sector
(
c
,
buffer
);
sema_down
(
&
c
->
completion_wait
);
lock_release
(
&
c
->
lock
);
}
static
struct
block_operations
ide_operations
=
{
ide_read
,
ide_write
};
/* Selects device D, waiting for it to become ready, and then
writes SEC_NO to the disk's sector selection registers. (We
use LBA mode.) */
static
void
select_sector
(
struct
ata_disk
*
d
,
block_sector_t
sec_no
)
{
struct
channel
*
c
=
d
->
channel
;
ASSERT
(
sec_no
<
(
1UL
<<
28
));
select_device_wait
(
d
);
outb
(
reg_nsect
(
c
),
1
);
outb
(
reg_lbal
(
c
),
sec_no
);
outb
(
reg_lbam
(
c
),
sec_no
>>
8
);
outb
(
reg_lbah
(
c
),
(
sec_no
>>
16
));
outb
(
reg_device
(
c
),
DEV_MBS
|
DEV_LBA
|
(
d
->
dev_no
==
1
?
DEV_DEV
:
0
)
|
(
sec_no
>>
24
));
}
/* Writes COMMAND to channel C and prepares for receiving a
completion interrupt. */
static
void
issue_pio_command
(
struct
channel
*
c
,
uint8_t
command
)
{
/* Interrupts must be enabled or our semaphore will never be
up'd by the completion handler. */
ASSERT
(
intr_get_level
()
==
INTR_ON
);
c
->
expecting_interrupt
=
true
;
outb
(
reg_command
(
c
),
command
);
}
/* Reads a sector from channel C's data register in PIO mode into
SECTOR, which must have room for BLOCK_SECTOR_SIZE bytes. */
static
void
input_sector
(
struct
channel
*
c
,
void
*
sector
)
{
insw
(
reg_data
(
c
),
sector
,
BLOCK_SECTOR_SIZE
/
2
);
}
/* Writes SECTOR to channel C's data register in PIO mode.
SECTOR must contain BLOCK_SECTOR_SIZE bytes. */
static
void
output_sector
(
struct
channel
*
c
,
const
void
*
sector
)
{
outsw
(
reg_data
(
c
),
sector
,
BLOCK_SECTOR_SIZE
/
2
);
}
/* Low-level ATA primitives. */
/* Wait up to 10 seconds for the controller to become idle, that
is, for the BSY and DRQ bits to clear in the status register.
As a side effect, reading the status register clears any
pending interrupt. */
static
void
wait_until_idle
(
const
struct
ata_disk
*
d
)
{
int
i
;
for
(
i
=
0
;
i
<
1000
;
i
++
)
{
if
((
inb
(
reg_status
(
d
->
channel
))
&
(
STA_BSY
|
STA_DRQ
))
==
0
)
return
;
timer_usleep
(
10
);
}
printf
(
"%s: idle timeout
\n
"
,
d
->
name
);
}
/* Wait up to 30 seconds for disk D to clear BSY,
and then return the status of the DRQ bit.
The ATA standards say that a disk may take as long as that to
complete its reset. */
static
bool
wait_while_busy
(
const
struct
ata_disk
*
d
)
{
struct
channel
*
c
=
d
->
channel
;
int
i
;
for
(
i
=
0
;
i
<
3000
;
i
++
)
{
if
(
i
==
700
)
printf
(
"%s: busy, waiting..."
,
d
->
name
);
if
(
!
(
inb
(
reg_alt_status
(
c
))
&
STA_BSY
))
{
if
(
i
>=
700
)
printf
(
"ok
\n
"
);
return
(
inb
(
reg_alt_status
(
c
))
&
STA_DRQ
)
!=
0
;
}
timer_msleep
(
10
);
}
printf
(
"failed
\n
"
);
return
false
;
}
/* Program D's channel so that D is now the selected disk. */
static
void
select_device
(
const
struct
ata_disk
*
d
)
{
struct
channel
*
c
=
d
->
channel
;
uint8_t
dev
=
DEV_MBS
;
if
(
d
->
dev_no
==
1
)
dev
|=
DEV_DEV
;
outb
(
reg_device
(
c
),
dev
);
inb
(
reg_alt_status
(
c
));
timer_nsleep
(
400
);
}
/* Select disk D in its channel, as select_device(), but wait for
the channel to become idle before and after. */
static
void
select_device_wait
(
const
struct
ata_disk
*
d
)
{
wait_until_idle
(
d
);
select_device
(
d
);
wait_until_idle
(
d
);
}
/* ATA interrupt handler. */
static
void
interrupt_handler
(
struct
intr_frame
*
f
)
{
struct
channel
*
c
;
for
(
c
=
channels
;
c
<
channels
+
CHANNEL_CNT
;
c
++
)
if
(
f
->
vec_no
==
c
->
irq
)
{
if
(
c
->
expecting_interrupt
)
{
inb
(
reg_status
(
c
));
/* Acknowledge interrupt. */
sema_up
(
&
c
->
completion_wait
);
/* Wake up waiter. */
}
else
printf
(
"%s: unexpected interrupt
\n
"
,
c
->
name
);
return
;
}
NOT_REACHED
();
}
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment