VirtualBox

source: vbox/trunk/src/VBox/Devices/Storage/DrvSCSIHost.cpp@ 26515

Last change on this file since 26515 was 26173, checked in by vboxsync, 15 years ago

PDM: s/pCfgHandle/pCfg/g - part 2.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 17.4 KB
Line 
1/* $Id: DrvSCSIHost.cpp 26173 2010-02-02 21:11:09Z vboxsync $ */
2/** @file
3 * VBox storage drivers: Host SCSI access driver.
4 */
5
6/*
7 * Copyright (C) 2006-2010 Sun Microsystems, Inc.
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
18 * Clara, CA 95054 USA or visit http://www.sun.com if you need
19 * additional information or have any questions.
20 */
21
22/*******************************************************************************
23* Header Files *
24*******************************************************************************/
25//#define DEBUG
26#define LOG_GROUP LOG_GROUP_DRV_SCSIHOST
27#include <VBox/pdmdrv.h>
28#include <VBox/pdmifs.h>
29#include <VBox/pdmthread.h>
30#include <VBox/scsi.h>
31#include <iprt/assert.h>
32#include <iprt/file.h>
33#include <iprt/mem.h>
34#include <iprt/req.h>
35#include <iprt/string.h>
36#include <iprt/uuid.h>
37
38#if defined(RT_OS_LINUX)
39# include <limits.h>
40# include <scsi/sg.h>
41# include <sys/ioctl.h>
42#endif
43
44#include "../Builtins.h"
45
46/**
47 * SCSI driver instance data.
48 *
49 * @implements PDMISCSICONNECTOR
50 */
51typedef struct DRVSCSIHOST
52{
53 /** Pointer driver instance. */
54 PPDMDRVINS pDrvIns;
55
56 /** Pointer to the SCSI port interface of the device above. */
57 PPDMISCSIPORT pDevScsiPort;
58 /** The SCSI connector interface . */
59 PDMISCSICONNECTOR ISCSIConnector;
60
61 /** PAth to the device file. */
62 char *pszDevicePath;
63 /** Handle to the device. */
64 RTFILE DeviceFile;
65
66 /** The dedicated I/O thread. */
67 PPDMTHREAD pAsyncIOThread;
68 /** Queue for passing the requests to the thread. */
69 PRTREQQUEUE pQueueRequests;
70} DRVSCSIHOST, *PDRVSCSIHOST;
71
72/** Converts a pointer to DRVSCSIHOST::ISCSIConnecotr to a PDRVSCSIHOST. */
73#define PDMISCSICONNECTOR_2_DRVSCSIHOST(pInterface) ( (PDRVSCSIHOST)((uintptr_t)pInterface - RT_OFFSETOF(DRVSCSIHOST, ISCSIConnector)) )
74
75#ifdef DEBUG
76/**
77 * Dumps a SCSI request structure for debugging purposes.
78 *
79 * @returns nothing.
80 * @param pRequest Pointer to the request to dump.
81 */
82static void drvscsihostDumpScsiRequest(PPDMSCSIREQUEST pRequest)
83{
84 Log(("Dump for pRequest=%#p Command: %s\n", pRequest, SCSICmdText(pRequest->pbCDB[0])));
85 Log(("cbCDB=%u\n", pRequest->cbCDB));
86 for (uint32_t i = 0; i < pRequest->cbCDB; i++)
87 Log(("pbCDB[%u]=%#x\n", i, pRequest->pbCDB[i]));
88 Log(("cbScatterGather=%u\n", pRequest->cbScatterGather));
89 Log(("cScatterGatherEntries=%u\n", pRequest->cScatterGatherEntries));
90 /* Print all scatter gather entries. */
91 for (uint32_t i = 0; i < pRequest->cScatterGatherEntries; i++)
92 {
93 Log(("ScatterGatherEntry[%u].cbSeg=%u\n", i, pRequest->paScatterGatherHead[i].cbSeg));
94 Log(("ScatterGatherEntry[%u].pvSeg=%#p\n", i, pRequest->paScatterGatherHead[i].pvSeg));
95 }
96 Log(("pvUser=%#p\n", pRequest->pvUser));
97}
98#endif
99
100/**
101 * Copy the content of a buffer to a scatter gather list
102 * copying only the amount of data which fits into the
103 * scatter gather list.
104 *
105 * @returns VBox status code.
106 * @param pRequest Pointer to the request which contains the S/G list entries.
107 * @param pvBuf Pointer to the buffer which should be copied.
108 * @param cbBuf Size of the buffer.
109 */
110static int drvscsihostScatterGatherListCopyFromBuffer(PPDMSCSIREQUEST pRequest, void *pvBuf, size_t cbBuf)
111{
112 unsigned cSGEntry = 0;
113 PPDMDATASEG pSGEntry = &pRequest->paScatterGatherHead[cSGEntry];
114 uint8_t *pu8Buf = (uint8_t *)pvBuf;
115
116 while (cSGEntry < pRequest->cScatterGatherEntries)
117 {
118 size_t cbToCopy = (cbBuf < pSGEntry->cbSeg) ? cbBuf : pSGEntry->cbSeg;
119
120 memcpy(pSGEntry->pvSeg, pu8Buf, cbToCopy);
121
122 cbBuf -= cbToCopy;
123 /* We finished. */
124 if (!cbBuf)
125 break;
126
127 /* Advance the buffer. */
128 pu8Buf += cbToCopy;
129
130 /* Go to the next entry in the list. */
131 pSGEntry++;
132 cSGEntry++;
133 }
134
135 return VINF_SUCCESS;
136}
137
138/**
139 * Set the sense and advanced sense key in the buffer for error conditions.
140 *
141 * @returns nothing.
142 * @param pRequest Pointer to the request which contains the sense buffer.
143 * @param uSCSISenseKey The sense key to set.
144 * @param uSCSIASC The advanced sense key to set.
145 */
146DECLINLINE(void) drvscsiCmdError(PPDMSCSIREQUEST pRequest, uint8_t uSCSISenseKey, uint8_t uSCSIASC)
147{
148 AssertMsg(pRequest->cbSenseBuffer >= 2, ("Sense buffer is not big enough\n"));
149 AssertMsg(pRequest->pbSenseBuffer, ("Sense buffer pointer is NULL\n"));
150 pRequest->pbSenseBuffer[0] = uSCSISenseKey;
151 pRequest->pbSenseBuffer[1] = uSCSIASC;
152}
153
154/**
155 * Sets the sense key for a status good condition.
156 *
157 * @returns nothing.
158 * @param pRequest Pointer to the request which contains the sense buffer.
159 */
160DECLINLINE(void) drvscsihostCmdOk(PPDMSCSIREQUEST pRequest)
161{
162 AssertMsg(pRequest->cbSenseBuffer >= 2, ("Sense buffer is not big enough\n"));
163 AssertMsg(pRequest->pbSenseBuffer, ("Sense buffer pointer is NULL\n"));
164 pRequest->pbSenseBuffer[0] = SCSI_SENSE_NONE;
165 pRequest->pbSenseBuffer[1] = SCSI_ASC_NONE;
166}
167
168/**
169 * Returns the transfer direction of the given command
170 * in case the device does not provide this info.
171 *
172 * @returns transfer direction of the command.
173 * SCSIHOSTTXDIR_NONE if no data is transfered.
174 * SCSIHOSTTXDIR_FROM_DEVICE if the data is read from the device.
175 * SCSIHOSTTXDIR_TO_DEVICE if the data is written to the device.
176 * @param uCommand The command byte.
177 */
178static unsigned drvscsihostGetTransferDirectionFromCommand(uint8_t uCommand)
179{
180 switch (uCommand)
181 {
182 case SCSI_INQUIRY:
183 case SCSI_REPORT_LUNS:
184 case SCSI_MODE_SENSE_6:
185 case SCSI_READ_TOC_PMA_ATIP:
186 case SCSI_READ_CAPACITY:
187 case SCSI_MODE_SENSE_10:
188 case SCSI_GET_EVENT_STATUS_NOTIFICATION:
189 case SCSI_GET_CONFIGURATION:
190 case SCSI_READ_10:
191 case SCSI_READ_12:
192 case SCSI_READ_BUFFER:
193 case SCSI_READ_BUFFER_CAPACITY:
194 case SCSI_READ_DISC_INFORMATION:
195 case SCSI_READ_DVD_STRUCTURE:
196 case SCSI_READ_FORMAT_CAPACITIES:
197 case SCSI_READ_SUBCHANNEL:
198 case SCSI_READ_TRACK_INFORMATION:
199 case SCSI_READ_CD:
200 case SCSI_READ_CD_MSF:
201 return PDMSCSIREQUESTTXDIR_FROM_DEVICE;
202 case SCSI_TEST_UNIT_READY:
203 case SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL:
204 case SCSI_START_STOP_UNIT:
205 return PDMSCSIREQUESTTXDIR_NONE;
206 case SCSI_WRITE_10:
207 case SCSI_WRITE_12:
208 case SCSI_WRITE_BUFFER:
209 return PDMSCSIREQUESTTXDIR_TO_DEVICE;
210 default:
211 AssertMsgFailed(("Command not known %#x\n", uCommand));
212 }
213
214 /* We should never get here in debug mode. */
215 AssertMsgFailed(("Impossible to get here!!!\n"));
216 return PDMSCSIREQUESTTXDIR_NONE; /* to make compilers happy. */
217}
218
219static int drvscsihostProcessRequestOne(PDRVSCSIHOST pThis, PPDMSCSIREQUEST pRequest)
220{
221 int rc = VINF_SUCCESS;
222 unsigned uTxDir;
223
224 LogFlowFunc(("Entered\n"));
225
226#ifdef DEBUG
227 drvscsihostDumpScsiRequest(pRequest);
228#endif
229
230 /* We implement only one device. */
231 if (pRequest->uLogicalUnit != 0)
232 {
233 switch (pRequest->pbCDB[0])
234 {
235 case SCSI_INQUIRY:
236 {
237 SCSIINQUIRYDATA ScsiInquiryReply;
238
239 memset(&ScsiInquiryReply, 0, sizeof(ScsiInquiryReply));
240
241 ScsiInquiryReply.u5PeripheralDeviceType = SCSI_INQUIRY_DATA_PERIPHERAL_DEVICE_TYPE_UNKNOWN;
242 ScsiInquiryReply.u3PeripheralQualifier = SCSI_INQUIRY_DATA_PERIPHERAL_QUALIFIER_NOT_CONNECTED_NOT_SUPPORTED;
243 drvscsihostScatterGatherListCopyFromBuffer(pRequest, &ScsiInquiryReply, sizeof(SCSIINQUIRYDATA));
244 drvscsihostCmdOk(pRequest);
245 break;
246 }
247 default:
248 AssertMsgFailed(("Command not implemented for attached device\n"));
249 drvscsiCmdError(pRequest, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_NONE);
250 }
251 }
252 else
253 {
254#if defined(RT_OS_LINUX)
255 sg_io_hdr_t ScsiIoReq;
256 sg_iovec_t *paSG = NULL;
257
258 /* Setup SCSI request. */
259 memset(&ScsiIoReq, 0, sizeof(sg_io_hdr_t));
260 ScsiIoReq.interface_id = 'S';
261
262 if (pRequest->uDataDirection == PDMSCSIREQUESTTXDIR_UNKNOWN)
263 uTxDir = drvscsihostGetTransferDirectionFromCommand(pRequest->pbCDB[0]);
264 else
265 uTxDir = pRequest->uDataDirection;
266
267 if (uTxDir == PDMSCSIREQUESTTXDIR_NONE)
268 ScsiIoReq.dxfer_direction = SG_DXFER_NONE;
269 else if (uTxDir == PDMSCSIREQUESTTXDIR_TO_DEVICE)
270 ScsiIoReq.dxfer_direction = SG_DXFER_TO_DEV;
271 else if (uTxDir == PDMSCSIREQUESTTXDIR_FROM_DEVICE)
272 ScsiIoReq.dxfer_direction = SG_DXFER_FROM_DEV;
273 else
274 AssertMsgFailed(("Invalid transfer direction %u\n", uTxDir));
275
276 ScsiIoReq.cmd_len = pRequest->cbCDB;
277 ScsiIoReq.mx_sb_len = pRequest->cbSenseBuffer;
278 ScsiIoReq.dxfer_len = pRequest->cbScatterGather;
279
280 if (pRequest->cScatterGatherEntries > 0)
281 {
282 if (pRequest->cScatterGatherEntries == 1)
283 {
284 ScsiIoReq.iovec_count = 0;
285 ScsiIoReq.dxferp = pRequest->paScatterGatherHead[0].pvSeg;
286 }
287 else
288 {
289 ScsiIoReq.iovec_count = pRequest->cScatterGatherEntries;
290
291 paSG = (sg_iovec_t *)RTMemAllocZ(pRequest->cScatterGatherEntries * sizeof(sg_iovec_t));
292 AssertPtrReturn(paSG, VERR_NO_MEMORY);
293
294 for (unsigned i = 0; i < pRequest->cScatterGatherEntries; i++)
295 {
296 paSG[i].iov_base = pRequest->paScatterGatherHead[i].pvSeg;
297 paSG[i].iov_len = pRequest->paScatterGatherHead[i].cbSeg;
298 }
299 ScsiIoReq.dxferp = paSG;
300 }
301 }
302
303 ScsiIoReq.cmdp = pRequest->pbCDB;
304 ScsiIoReq.sbp = pRequest->pbSenseBuffer;
305 ScsiIoReq.timeout = UINT_MAX;
306 ScsiIoReq.flags |= SG_FLAG_DIRECT_IO;
307
308 /** Issue command. */
309 rc = ioctl(pThis->DeviceFile, SG_IO, &ScsiIoReq);
310 if (rc < 0)
311 {
312 AssertMsgFailed(("Ioctl failed with rc=%d\n", rc));
313 }
314
315 /* Request processed successfully. */
316 Log(("Command successfully processed\n"));
317 if (ScsiIoReq.iovec_count > 0)
318 RTMemFree(paSG);
319#endif
320 }
321 /* Notify device that request finished. */
322 rc = pThis->pDevScsiPort->pfnSCSIRequestCompleted(pThis->pDevScsiPort, pRequest, SCSI_STATUS_OK);
323 AssertMsgRC(rc, ("Notifying device above failed rc=%Rrc\n", rc));
324
325 return rc;
326
327}
328
329/**
330 * Request function to wakeup the thread.
331 *
332 * @returns VWRN_STATE_CHANGED.
333 */
334static int drvscsihostAsyncIOLoopWakeupFunc(void)
335{
336 return VWRN_STATE_CHANGED;
337}
338
339/**
340 * The thread function which processes the requests asynchronously.
341 *
342 * @returns VBox status code.
343 * @param pDrvIns Pointer to the device instance data.
344 * @param pThread Pointer to the thread instance data.
345 */
346static int drvscsihostAsyncIOLoop(PPDMDRVINS pDrvIns, PPDMTHREAD pThread)
347{
348 int rc = VINF_SUCCESS;
349 PDRVSCSIHOST pThis = PDMINS_2_DATA(pDrvIns, PDRVSCSIHOST);
350
351 LogFlowFunc(("Entering async IO loop.\n"));
352
353 if (pThread->enmState == PDMTHREADSTATE_INITIALIZING)
354 return VINF_SUCCESS;
355
356 while (pThread->enmState == PDMTHREADSTATE_RUNNING)
357 {
358 rc = RTReqProcess(pThis->pQueueRequests, RT_INDEFINITE_WAIT);
359 AssertMsg(rc == VWRN_STATE_CHANGED, ("Left RTReqProcess and error code is not VWRN_STATE_CHANGED rc=%Rrc\n", rc));
360 }
361
362 return VINF_SUCCESS;
363}
364
365static int drvscsihostAsyncIOLoopWakeup(PPDMDRVINS pDrvIns, PPDMTHREAD pThread)
366{
367 int rc;
368 PDRVSCSIHOST pThis = PDMINS_2_DATA(pDrvIns, PDRVSCSIHOST);
369 PRTREQ pReq;
370
371 AssertMsgReturn(pThis->pQueueRequests, ("pQueueRequests is NULL\n"), VERR_INVALID_STATE);
372
373 rc = RTReqCall(pThis->pQueueRequests, &pReq, 10000 /* 10 sec. */, (PFNRT)drvscsihostAsyncIOLoopWakeupFunc, 0);
374 AssertMsgRC(rc, ("Inserting request into queue failed rc=%Rrc\n"));
375
376 return rc;
377}
378
379/* -=-=-=-=- ISCSIConnector -=-=-=-=- */
380
381/** @copydoc PDMISCSICONNECTOR::pfnSCSIRequestSend. */
382static DECLCALLBACK(int) drvscsihostRequestSend(PPDMISCSICONNECTOR pInterface, PPDMSCSIREQUEST pSCSIRequest)
383{
384 int rc;
385 PDRVSCSIHOST pThis = PDMISCSICONNECTOR_2_DRVSCSIHOST(pInterface);
386 PRTREQ pReq;
387
388 AssertMsgReturn(pThis->pQueueRequests, ("pQueueRequests is NULL\n"), VERR_INVALID_STATE);
389
390 rc = RTReqCallEx(pThis->pQueueRequests, &pReq, 0, RTREQFLAGS_NO_WAIT, (PFNRT)drvscsihostProcessRequestOne, 2, pThis, pSCSIRequest);
391 AssertMsgReturn(RT_SUCCESS(rc), ("Inserting request into queue failed rc=%Rrc\n", rc), rc);
392
393 return VINF_SUCCESS;
394}
395
396/* -=-=-=-=- PDMIBASE -=-=-=-=- */
397
398/**
399 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
400 */
401static DECLCALLBACK(void *) drvscsihostQueryInterface(PPDMIBASE pInterface, const char *pszIID)
402{
403 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
404 PDRVSCSIHOST pThis = PDMINS_2_DATA(pDrvIns, PDRVSCSIHOST);
405
406 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
407 PDMIBASE_RETURN_INTERFACE(pszIID, PDMISCSICONNECTOR, &pThis->ISCSIConnector);
408 return NULL;
409}
410
411/* -=-=-=-=- PDMDRVREG -=-=-=-=- */
412
413/**
414 * Destruct a driver instance.
415 *
416 * Most VM resources are freed by the VM. This callback is provided so that any non-VM
417 * resources can be freed correctly.
418 *
419 * @param pDrvIns The driver instance data.
420 */
421static DECLCALLBACK(void) drvscsihostDestruct(PPDMDRVINS pDrvIns)
422{
423 PDRVSCSIHOST pThis = PDMINS_2_DATA(pDrvIns, PDRVSCSIHOST);
424 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
425
426 if (pThis->DeviceFile != NIL_RTFILE)
427 RTFileClose(pThis->DeviceFile);
428
429 if (pThis->pszDevicePath)
430 MMR3HeapFree(pThis->pszDevicePath);
431
432 if (pThis->pQueueRequests)
433 {
434 int rc = RTReqDestroyQueue(pThis->pQueueRequests);
435 AssertMsgRC(rc, ("Failed to destroy queue rc=%Rrc\n", rc));
436 }
437
438}
439
440/**
441 * Construct a block driver instance.
442 *
443 * @copydoc FNPDMDRVCONSTRUCT
444 */
445static DECLCALLBACK(int) drvscsihostConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
446{
447 PDRVSCSIHOST pThis = PDMINS_2_DATA(pDrvIns, PDRVSCSIHOST);
448 LogFlowFunc(("pDrvIns=%#p pCfg=%#p\n", pDrvIns, pCfg));
449 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
450
451 /*
452 * Read the configuration.
453 */
454 if (!CFGMR3AreValuesValid(pCfg, "DevicePath\0"))
455 return PDMDRV_SET_ERROR(pDrvIns, VERR_PDM_DRVINS_UNKNOWN_CFG_VALUES,
456 N_("Invalid configuration for host scsi access driver"));
457
458 /*
459 * Initialize interfaces.
460 */
461 pDrvIns->IBase.pfnQueryInterface = drvscsihostQueryInterface;
462 pThis->ISCSIConnector.pfnSCSIRequestSend = drvscsihostRequestSend;
463 pThis->pDrvIns = pDrvIns;
464 pThis->DeviceFile = NIL_RTFILE;
465
466 /* Query the SCSI port interface above. */
467 pThis->pDevScsiPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMISCSIPORT);
468 AssertMsgReturn(pThis->pDevScsiPort, ("Missing SCSI port interface above\n"), VERR_PDM_MISSING_INTERFACE);
469
470 /* Create request queue. */
471 int rc = RTReqCreateQueue(&pThis->pQueueRequests);
472 AssertMsgReturn(RT_SUCCESS(rc), ("Failed to create request queue rc=%Rrc\n"), rc);
473
474 /* Open the device. */
475 rc = CFGMR3QueryStringAlloc(pCfg, "DevicePath", &pThis->pszDevicePath);
476 if (RT_FAILURE(rc))
477 return PDMDRV_SET_ERROR(pDrvIns, rc,
478 N_("Configuration error: Failed to get the \"DevicePath\" value"));
479
480 rc = RTFileOpen(&pThis->DeviceFile, pThis->pszDevicePath, RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
481 if (RT_FAILURE(rc))
482 return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
483 N_("DrvSCSIHost#%d: Failed to open device '%s'"), pDrvIns->iInstance, pThis->pszDevicePath);
484
485 /* Create I/O thread. */
486 rc = PDMDrvHlpPDMThreadCreate(pDrvIns, &pThis->pAsyncIOThread, pThis, drvscsihostAsyncIOLoop,
487 drvscsihostAsyncIOLoopWakeup, 0, RTTHREADTYPE_IO, "SCSI async IO");
488 AssertMsgReturn(RT_SUCCESS(rc), ("Failed to create async I/O thread rc=%Rrc\n"), rc);
489
490 return VINF_SUCCESS;
491}
492
493/**
494 * SCSI driver registration record.
495 */
496const PDMDRVREG g_DrvSCSIHost =
497{
498 /* u32Version */
499 PDM_DRVREG_VERSION,
500 /* szName */
501 "SCSIHost",
502 /* szRCMod */
503 "",
504 /* szR0Mod */
505 "",
506 /* pszDescription */
507 "Host SCSI driver.",
508 /* fFlags */
509 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
510 /* fClass. */
511 PDM_DRVREG_CLASS_SCSI,
512 /* cMaxInstances */
513 ~0,
514 /* cbInstance */
515 sizeof(DRVSCSIHOST),
516 /* pfnConstruct */
517 drvscsihostConstruct,
518 /* pfnDestruct */
519 drvscsihostDestruct,
520 /* pfnRelocate */
521 NULL,
522 /* pfnIOCtl */
523 NULL,
524 /* pfnPowerOn */
525 NULL,
526 /* pfnReset */
527 NULL,
528 /* pfnSuspend */
529 NULL,
530 /* pfnResume */
531 NULL,
532 /* pfnAttach */
533 NULL,
534 /* pfnDetach */
535 NULL,
536 /* pfnPowerOff */
537 NULL,
538 /* pfnSoftReset */
539 NULL,
540 /* u32EndVersion */
541 PDM_DRVREG_VERSION
542};
543
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette