Skip to main content

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 clear_nexus_state(&self) {
92        // This wrapper keeps its own sense slot in front of the device's, so
93        // both have to be cleared or a REQUEST SENSE still answers from here.
94        self.sense_data.set(None);
95        self.inner.clear_nexus_state();
96    }
97
98    fn execute_scsi<'a>(
99        &'a self,
100        external_data: &'a RequestBuffers<'a>,
101        request: &'a Request,
102    ) -> StackFuture<'a, ScsiResult, { ASYNC_SCSI_DISK_STACK_SIZE }> {
103        StackFuture::from_or_box(async move {
104            let op = request.scsiop();
105            let result = match op {
106                ScsiOp::READ
107                | ScsiOp::READ12
108                | ScsiOp::TEST_UNIT_READY
109                | ScsiOp::READ_TOC
110                | ScsiOp::INQUIRY
111                | ScsiOp::GET_CONFIGURATION
112                | ScsiOp::MODE_SENSE
113                | ScsiOp::MODE_SENSE10
114                | ScsiOp::READ_DVD_STRUCTURE
115                | ScsiOp::READ_CAPACITY
116                | ScsiOp::GET_EVENT_STATUS
117                | ScsiOp::MEDIUM_REMOVAL
118                | ScsiOp::START_STOP_UNIT => self.inner.execute_scsi(external_data, request).await,
119                ScsiOp::REQUEST_SENSE => self.atapi_request_sense(external_data, request),
120                ScsiOp::REPORT_LUNS => self.atapi_report_luns(external_data, request),
121                ScsiOp::SEEK => {
122                    // NO-OP's
123                    self.atapi_noop()
124                }
125                _ => self.atapi_illegal_cmd(),
126            };
127
128            self.sense_data.set(result.sense_data.as_ref());
129            tracing::debug!(scsi_result = ?result, ?op, "Atapi execute_scsi_async.");
130            result
131        })
132    }
133}
134
135impl AtapiScsiDisk {
136    pub fn new(disk: Arc<dyn AsyncScsiDisk>) -> Self {
137        AtapiScsiDisk {
138            inner: disk,
139            sense_data: Default::default(),
140        }
141    }
142
143    fn atapi_request_sense(
144        &self,
145        external_data: &RequestBuffers<'_>,
146        request: &Request,
147    ) -> ScsiResult {
148        let cdb = scsi::CdbInquiry::read_from_prefix(&request.cdb[..])
149            .unwrap()
150            .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
151        let allocation_length = cdb.allocation_length.get() as usize;
152
153        let min = size_of::<scsi::SenseDataHeader>();
154        if allocation_length < min || allocation_length > external_data.len() {
155            tracelimit::error_ratelimited!(
156                allocation_length,
157                min,
158                external_data_len = external_data.len(),
159                "srb error"
160            );
161            return ScsiResult {
162                scsi_status: ScsiStatus::CHECK_CONDITION,
163                srb_status: SrbStatus::ERROR,
164                tx: 0,
165                sense_data: None,
166            };
167        }
168
169        let sense = self.sense_data.take().unwrap_or_else(|| {
170            scsi::SenseData::new(SenseKey::NO_SENSE, AdditionalSenseCode::NO_SENSE, 0x00)
171        });
172
173        let tx = std::cmp::min(allocation_length, size_of::<scsi::SenseData>());
174        let result = external_data.writer().write(&sense.as_bytes()[..tx]);
175
176        match result {
177            Err(err) => {
178                tracelimit::error_ratelimited!(
179                    ?err,
180                    "SCSIOP_REQUEST_SENSE hit memory access error"
181                );
182                ScsiResult {
183                    scsi_status: ScsiStatus::CHECK_CONDITION,
184                    srb_status: SrbStatus::INVALID_REQUEST,
185                    tx: 0,
186                    sense_data: Some(illegal_request_sense(AdditionalSenseCode::INVALID_CDB)),
187                }
188            }
189            Ok(_) => ScsiResult {
190                scsi_status: ScsiStatus::GOOD,
191                srb_status: SrbStatus::SUCCESS,
192                tx,
193                sense_data: None,
194            },
195        }
196    }
197
198    fn atapi_report_luns(
199        &self,
200        external_data: &RequestBuffers<'_>,
201        request: &Request,
202    ) -> ScsiResult {
203        let cdb = scsi::ReportLuns::read_from_prefix(&request.cdb[..])
204            .unwrap()
205            .0; // TODO: zerocopy: use-rest-of-range (https://github.com/microsoft/openvmm/issues/759)
206        let allocation_length = cdb.allocation_length.get() as usize;
207        if allocation_length == 0 {
208            return ScsiResult {
209                scsi_status: ScsiStatus::GOOD,
210                srb_status: SrbStatus::SUCCESS,
211                tx: 0,
212                sense_data: None,
213            };
214        }
215
216        // Return LUN list with only one LUN ID 0
217        let mut data: Vec<u64> = vec![0; 2];
218        let tx = data.as_bytes().len();
219        if allocation_length < tx || allocation_length > external_data.len() {
220            tracelimit::error_ratelimited!(
221                allocation_length,
222                tx,
223                external_data_len = external_data.len(),
224                "srb error"
225            );
226            return ScsiResult {
227                scsi_status: ScsiStatus::CHECK_CONDITION,
228                srb_status: SrbStatus::ERROR,
229                tx: 0,
230                sense_data: None,
231            };
232        }
233
234        const HEADER_SIZE: usize = size_of::<scsi::LunList>();
235        let header = scsi::LunList {
236            length: 8.into(),
237            reserved: [0; 4],
238        };
239        data.as_mut_bytes()[..HEADER_SIZE].copy_from_slice(header.as_bytes());
240        data[1].as_mut_bytes()[..2].copy_from_slice(&0_u16.to_be_bytes());
241
242        let result = external_data.writer().write(&data.as_bytes()[..tx]);
243
244        match result {
245            Err(err) => {
246                tracelimit::error_ratelimited!(?err, "SCSIOP_REPORT_LUNS hit memory access error");
247                ScsiResult {
248                    scsi_status: ScsiStatus::CHECK_CONDITION,
249                    srb_status: SrbStatus::INVALID_REQUEST,
250                    tx: 0,
251                    sense_data: Some(illegal_request_sense(AdditionalSenseCode::INVALID_CDB)),
252                }
253            }
254            Ok(_) => ScsiResult {
255                scsi_status: ScsiStatus::GOOD,
256                srb_status: SrbStatus::SUCCESS,
257                tx,
258                sense_data: None,
259            },
260        }
261    }
262
263    fn atapi_noop(&self) -> ScsiResult {
264        ScsiResult {
265            scsi_status: ScsiStatus::GOOD,
266            srb_status: SrbStatus::SUCCESS,
267            tx: 0,
268            sense_data: None,
269        }
270    }
271
272    fn atapi_illegal_cmd(&self) -> ScsiResult {
273        ScsiResult {
274            scsi_status: ScsiStatus::CHECK_CONDITION,
275            srb_status: SrbStatus::INVALID_REQUEST,
276            tx: 0,
277            sense_data: Some(illegal_request_sense(AdditionalSenseCode::ILLEGAL_COMMAND)),
278        }
279    }
280}