scsidisk/
atapi_scsi.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Implements ATAPI SCSI command handler for an IDE CD_ROM, wrapping a
5//! [`AsyncScsiDisk`].
6//!
7
8use crate::SenseDataSlot;
9use crate::illegal_request_sense;
10use guestmem::MemoryWrite;
11use inspect::Inspect;
12use scsi::AdditionalSenseCode;
13use scsi::ScsiOp;
14use scsi::ScsiStatus;
15use scsi::SenseKey;
16use scsi::srb::SrbStatus;
17use scsi_buffers::RequestBuffers;
18use scsi_core::ASYNC_SCSI_DISK_STACK_SIZE;
19use scsi_core::AsyncScsiDisk;
20use scsi_core::Request;
21use scsi_core::ScsiResult;
22use scsi_core::ScsiSaveRestore;
23use scsi_core::save_restore::SavedSenseData;
24use scsi_core::save_restore::ScsiDvdSavedState;
25use scsi_core::save_restore::ScsiSavedState;
26use scsi_defs as scsi;
27use stackfuture::StackFuture;
28use std::sync::Arc;
29use vmcore::save_restore::RestoreError;
30use vmcore::save_restore::SaveError;
31use zerocopy::FromBytes;
32use zerocopy::IntoBytes;
33
34/// A wrapper to filter and redirect ATAPI SCSI commands from an IDE ISO to inner [`AsyncScsiDisk`].
35#[derive(Inspect)]
36pub struct AtapiScsiDisk {
37    #[inspect(flatten)]
38    inner: Arc<dyn AsyncScsiDisk>,
39    #[inspect(skip)]
40    sense_data: SenseDataSlot,
41}
42
43impl ScsiSaveRestore for AtapiScsiDisk {
44    fn save(&self) -> Result<Option<ScsiSavedState>, SaveError> {
45        let sense = self.sense_data.get();
46        let sense_data = sense.map(|sense| SavedSenseData {
47            sense_key: sense.header.sense_key.0,
48            additional_sense_code: sense.additional_sense_code.0,
49            additional_sense_code_qualifier: sense.additional_sense_code_qualifier,
50        });
51        let state = self.inner.save()?.unwrap();
52        match state {
53            ScsiSavedState::ScsiDvd(mut state) => {
54                state.sense_data = sense_data;
55                Ok(Some(ScsiSavedState::ScsiDvd(state)))
56            }
57            _ => Err(SaveError::InvalidChildSavedState(anyhow::anyhow!(
58                "saved state didn't match expected ScsiSavedState::ScsiDvd"
59            ))),
60        }
61    }
62
63    fn restore(&self, state: &ScsiSavedState) -> Result<(), RestoreError> {
64        self.inner.restore(state)?;
65        match state {
66            ScsiSavedState::ScsiDvd(state) => {
67                let ScsiDvdSavedState { sense_data, .. } = state;
68                // restore sense data
69                self.sense_data.set(
70                    sense_data
71                        .map(|sense| {
72                            scsi::SenseData::new(
73                                SenseKey(sense.sense_key),
74                                AdditionalSenseCode(sense.additional_sense_code),
75                                sense.additional_sense_code_qualifier,
76                            )
77                        })
78                        .as_ref(),
79                );
80
81                Ok(())
82            }
83            _ => Err(RestoreError::InvalidSavedState(anyhow::anyhow!(
84                "saved state didn't match expected ScsiSavedState::ScsiDvd"
85            ))),
86        }
87    }
88}
89
90impl AsyncScsiDisk for AtapiScsiDisk {
91    fn execute_scsi<'a>(
92        &'a self,
93        external_data: &'a RequestBuffers<'a>,
94        request: &'a Request,
95    ) -> StackFuture<'a, ScsiResult, { ASYNC_SCSI_DISK_STACK_SIZE }> {
96        StackFuture::from_or_box(async move {
97            let op = request.scsiop();
98            let result = match op {
99                ScsiOp::READ
100                | ScsiOp::READ12
101                | ScsiOp::TEST_UNIT_READY
102                | ScsiOp::READ_TOC
103                | ScsiOp::INQUIRY
104                | ScsiOp::GET_CONFIGURATION
105                | ScsiOp::MODE_SENSE
106                | ScsiOp::MODE_SENSE10
107                | ScsiOp::READ_DVD_STRUCTURE
108                | ScsiOp::READ_CAPACITY
109                | ScsiOp::GET_EVENT_STATUS
110                | ScsiOp::MEDIUM_REMOVAL
111                | ScsiOp::START_STOP_UNIT => self.inner.execute_scsi(external_data, request).await,
112                ScsiOp::REQUEST_SENSE => self.atapi_request_sense(external_data, request),
113                ScsiOp::REPORT_LUNS => self.atapi_report_luns(external_data, request),
114                ScsiOp::SEEK => {
115                    // NO-OP's
116                    self.atapi_noop()
117                }
118                _ => self.atapi_illegal_cmd(),
119            };
120
121            self.sense_data.set(result.sense_data.as_ref());
122            tracing::debug!(scsi_result = ?result, ?op, "Atapi execute_scsi_async.");
123            result
124        })
125    }
126}
127
128impl AtapiScsiDisk {
129    pub fn new(disk: Arc<dyn AsyncScsiDisk>) -> Self {
130        AtapiScsiDisk {
131            inner: disk,
132            sense_data: Default::default(),
133        }
134    }
135
136    fn atapi_request_sense(
137        &self,
138        external_data: &RequestBuffers<'_>,
139        request: &Request,
140    ) -> ScsiResult {
141        let cdb = scsi::CdbInquiry::read_from_prefix(&request.cdb[..])
142            .unwrap()
143            .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
144        let allocation_length = cdb.allocation_length.get() as usize;
145
146        let min = size_of::<scsi::SenseDataHeader>();
147        if allocation_length < min || allocation_length > external_data.len() {
148            tracelimit::error_ratelimited!(
149                allocation_length,
150                min,
151                external_data_len = external_data.len(),
152                "srb error"
153            );
154            return ScsiResult {
155                scsi_status: ScsiStatus::CHECK_CONDITION,
156                srb_status: SrbStatus::ERROR,
157                tx: 0,
158                sense_data: None,
159            };
160        }
161
162        let sense = self.sense_data.take().unwrap_or_else(|| {
163            scsi::SenseData::new(SenseKey::NO_SENSE, AdditionalSenseCode::NO_SENSE, 0x00)
164        });
165
166        let tx = std::cmp::min(allocation_length, size_of::<scsi::SenseData>());
167        let result = external_data.writer().write(&sense.as_bytes()[..tx]);
168
169        match result {
170            Err(err) => {
171                tracelimit::error_ratelimited!(
172                    ?err,
173                    "SCSIOP_REQUEST_SENSE hit memory access error"
174                );
175                ScsiResult {
176                    scsi_status: ScsiStatus::CHECK_CONDITION,
177                    srb_status: SrbStatus::INVALID_REQUEST,
178                    tx: 0,
179                    sense_data: Some(illegal_request_sense(AdditionalSenseCode::INVALID_CDB)),
180                }
181            }
182            Ok(_) => ScsiResult {
183                scsi_status: ScsiStatus::GOOD,
184                srb_status: SrbStatus::SUCCESS,
185                tx,
186                sense_data: None,
187            },
188        }
189    }
190
191    fn atapi_report_luns(
192        &self,
193        external_data: &RequestBuffers<'_>,
194        request: &Request,
195    ) -> ScsiResult {
196        let cdb = scsi::ReportLuns::read_from_prefix(&request.cdb[..])
197            .unwrap()
198            .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
199        let allocation_length = cdb.allocation_length.get() as usize;
200        if allocation_length == 0 {
201            return ScsiResult {
202                scsi_status: ScsiStatus::GOOD,
203                srb_status: SrbStatus::SUCCESS,
204                tx: 0,
205                sense_data: None,
206            };
207        }
208
209        // Return LUN list with only one LUN ID 0
210        let mut data: Vec<u64> = vec![0; 2];
211        let tx = data.as_bytes().len();
212        if allocation_length < tx || allocation_length > external_data.len() {
213            tracelimit::error_ratelimited!(
214                allocation_length,
215                tx,
216                external_data_len = external_data.len(),
217                "srb error"
218            );
219            return ScsiResult {
220                scsi_status: ScsiStatus::CHECK_CONDITION,
221                srb_status: SrbStatus::ERROR,
222                tx: 0,
223                sense_data: None,
224            };
225        }
226
227        const HEADER_SIZE: usize = size_of::<scsi::LunList>();
228        let header = scsi::LunList {
229            length: 8.into(),
230            reserved: [0; 4],
231        };
232        data.as_mut_bytes()[..HEADER_SIZE].copy_from_slice(header.as_bytes());
233        data[1].as_mut_bytes()[..2].copy_from_slice(&0_u16.to_be_bytes());
234
235        let result = external_data.writer().write(&data.as_bytes()[..tx]);
236
237        match result {
238            Err(err) => {
239                tracelimit::error_ratelimited!(?err, "SCSIOP_REPORT_LUNS hit memory access error");
240                ScsiResult {
241                    scsi_status: ScsiStatus::CHECK_CONDITION,
242                    srb_status: SrbStatus::INVALID_REQUEST,
243                    tx: 0,
244                    sense_data: Some(illegal_request_sense(AdditionalSenseCode::INVALID_CDB)),
245                }
246            }
247            Ok(_) => ScsiResult {
248                scsi_status: ScsiStatus::GOOD,
249                srb_status: SrbStatus::SUCCESS,
250                tx,
251                sense_data: None,
252            },
253        }
254    }
255
256    fn atapi_noop(&self) -> ScsiResult {
257        ScsiResult {
258            scsi_status: ScsiStatus::GOOD,
259            srb_status: SrbStatus::SUCCESS,
260            tx: 0,
261            sense_data: None,
262        }
263    }
264
265    fn atapi_illegal_cmd(&self) -> ScsiResult {
266        ScsiResult {
267            scsi_status: ScsiStatus::CHECK_CONDITION,
268            srb_status: SrbStatus::INVALID_REQUEST,
269            tx: 0,
270            sense_data: Some(illegal_request_sense(AdditionalSenseCode::ILLEGAL_COMMAND)),
271        }
272    }
273}