Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27//#define PATA_DEVICE_DEBUG
28
29#include <Kernel/Devices/PATAChannel.h>
30#include <Kernel/Devices/PATADiskDevice.h>
31#include <Kernel/FileSystem/FileDescription.h>
32
33namespace Kernel {
34
35NonnullRefPtr<PATADiskDevice> PATADiskDevice::create(PATAChannel& channel, DriveType type, int major, int minor)
36{
37 return adopt(*new PATADiskDevice(channel, type, major, minor));
38}
39
40PATADiskDevice::PATADiskDevice(PATAChannel& channel, DriveType type, int major, int minor)
41 : BlockDevice(major, minor, 512)
42 , m_drive_type(type)
43 , m_channel(channel)
44{
45}
46
47PATADiskDevice::~PATADiskDevice()
48{
49}
50
51const char* PATADiskDevice::class_name() const
52{
53 return "PATADiskDevice";
54}
55
56bool PATADiskDevice::read_blocks(unsigned index, u16 count, u8* out)
57{
58 if (m_channel.m_bus_master_base && m_channel.m_dma_enabled.resource())
59 return read_sectors_with_dma(index, count, out);
60 return read_sectors(index, count, out);
61}
62
63bool PATADiskDevice::write_blocks(unsigned index, u16 count, const u8* data)
64{
65 if (m_channel.m_bus_master_base && m_channel.m_dma_enabled.resource())
66 return write_sectors_with_dma(index, count, data);
67 for (unsigned i = 0; i < count; ++i) {
68 if (!write_sectors(index + i, 1, data + i * 512))
69 return false;
70 }
71 return true;
72}
73
74void PATADiskDevice::set_drive_geometry(u16 cyls, u16 heads, u16 spt)
75{
76 m_cylinders = cyls;
77 m_heads = heads;
78 m_sectors_per_track = spt;
79}
80
81ssize_t PATADiskDevice::read(FileDescription& fd, u8* outbuf, ssize_t len)
82{
83 unsigned index = fd.offset() / block_size();
84 u16 whole_blocks = len / block_size();
85 ssize_t remaining = len % block_size();
86
87 unsigned blocks_per_page = PAGE_SIZE / block_size();
88
89 // PATAChannel will chuck a wobbly if we try to read more than PAGE_SIZE
90 // at a time, because it uses a single page for its DMA buffer.
91 if (whole_blocks >= blocks_per_page) {
92 whole_blocks = blocks_per_page;
93 remaining = 0;
94 }
95
96#ifdef PATA_DEVICE_DEBUG
97 kprintf("PATADiskDevice::read() index=%d whole_blocks=%d remaining=%d\n", index, whole_blocks, remaining);
98#endif
99
100 if (whole_blocks > 0) {
101 if (!read_blocks(index, whole_blocks, outbuf))
102 return -1;
103 }
104
105 off_t pos = whole_blocks * block_size();
106
107 if (remaining > 0) {
108 auto buf = ByteBuffer::create_uninitialized(block_size());
109 if (!read_blocks(index + whole_blocks, 1, buf.data()))
110 return pos;
111 memcpy(&outbuf[pos], buf.data(), remaining);
112 }
113
114 return pos + remaining;
115}
116
117bool PATADiskDevice::can_read(const FileDescription& fd) const
118{
119 return static_cast<unsigned>(fd.offset()) < (m_cylinders * m_heads * m_sectors_per_track * block_size());
120}
121
122ssize_t PATADiskDevice::write(FileDescription& fd, const u8* inbuf, ssize_t len)
123{
124 unsigned index = fd.offset() / block_size();
125 u16 whole_blocks = len / block_size();
126 ssize_t remaining = len % block_size();
127
128 unsigned blocks_per_page = PAGE_SIZE / block_size();
129
130 // PATAChannel will chuck a wobbly if we try to write more than PAGE_SIZE
131 // at a time, because it uses a single page for its DMA buffer.
132 if (whole_blocks >= blocks_per_page) {
133 whole_blocks = blocks_per_page;
134 remaining = 0;
135 }
136
137#ifdef PATA_DEVICE_DEBUG
138 kprintf("PATADiskDevice::write() index=%d whole_blocks=%d remaining=%d\n", index, whole_blocks, remaining);
139#endif
140
141 if (whole_blocks > 0) {
142 if (!write_blocks(index, whole_blocks, inbuf))
143 return -1;
144 }
145
146 off_t pos = whole_blocks * block_size();
147
148 // since we can only write in block_size() increments, if we want to do a
149 // partial write, we have to read the block's content first, modify it,
150 // then write the whole block back to the disk.
151 if (remaining > 0) {
152 auto buf = ByteBuffer::create_zeroed(block_size());
153 if (!read_blocks(index + whole_blocks, 1, buf.data()))
154 return pos;
155 memcpy(buf.data(), &inbuf[pos], remaining);
156 if (!write_blocks(index + whole_blocks, 1, buf.data()))
157 return pos;
158 }
159
160 return pos + remaining;
161}
162
163bool PATADiskDevice::can_write(const FileDescription& fd) const
164{
165 return static_cast<unsigned>(fd.offset()) < (m_cylinders * m_heads * m_sectors_per_track * block_size());
166}
167
168bool PATADiskDevice::read_sectors_with_dma(u32 lba, u16 count, u8* outbuf)
169{
170 return m_channel.ata_read_sectors_with_dma(lba, count, outbuf, is_slave());
171}
172
173bool PATADiskDevice::read_sectors(u32 start_sector, u16 count, u8* outbuf)
174{
175 return m_channel.ata_read_sectors(start_sector, count, outbuf, is_slave());
176}
177
178bool PATADiskDevice::write_sectors_with_dma(u32 lba, u16 count, const u8* inbuf)
179{
180 return m_channel.ata_write_sectors_with_dma(lba, count, inbuf, is_slave());
181}
182
183bool PATADiskDevice::write_sectors(u32 start_sector, u16 count, const u8* inbuf)
184{
185 return m_channel.ata_write_sectors(start_sector, count, inbuf, is_slave());
186}
187
188bool PATADiskDevice::is_slave() const
189{
190 return m_drive_type == DriveType::Slave;
191}
192
193}