diff options
Diffstat (limited to '')
-rw-r--r-- | Documentation/scsi/libsas.rst | 462 |
1 files changed, 462 insertions, 0 deletions
diff --git a/Documentation/scsi/libsas.rst b/Documentation/scsi/libsas.rst new file mode 100644 index 000000000..ea63ab3a9 --- /dev/null +++ b/Documentation/scsi/libsas.rst @@ -0,0 +1,462 @@ +.. SPDX-License-Identifier: GPL-2.0 + +========= +SAS Layer +========= + +The SAS Layer is a management infrastructure which manages +SAS LLDDs. It sits between SCSI Core and SAS LLDDs. The +layout is as follows: while SCSI Core is concerned with +SAM/SPC issues, and a SAS LLDD+sequencer is concerned with +phy/OOB/link management, the SAS layer is concerned with: + + * SAS Phy/Port/HA event management (LLDD generates, + SAS Layer processes), + * SAS Port management (creation/destruction), + * SAS Domain discovery and revalidation, + * SAS Domain device management, + * SCSI Host registration/unregistration, + * Device registration with SCSI Core (SAS) or libata + (SATA), and + * Expander management and exporting expander control + to user space. + +A SAS LLDD is a PCI device driver. It is concerned with +phy/OOB management, and vendor specific tasks and generates +events to the SAS layer. + +The SAS Layer does most SAS tasks as outlined in the SAS 1.1 +spec. + +The sas_ha_struct describes the SAS LLDD to the SAS layer. +Most of it is used by the SAS Layer but a few fields need to +be initialized by the LLDDs. + +After initializing your hardware, from the probe() function +you call sas_register_ha(). It will register your LLDD with +the SCSI subsystem, creating a SCSI host and it will +register your SAS driver with the sysfs SAS tree it creates. +It will then return. Then you enable your phys to actually +start OOB (at which point your driver will start calling the +notify_* event callbacks). + +Structure descriptions +====================== + +``struct sas_phy`` +------------------ + +Normally this is statically embedded to your driver's +phy structure:: + + struct my_phy { + blah; + struct sas_phy sas_phy; + bleh; + }; + +And then all the phys are an array of my_phy in your HA +struct (shown below). + +Then as you go along and initialize your phys you also +initialize the sas_phy struct, along with your own +phy structure. + +In general, the phys are managed by the LLDD and the ports +are managed by the SAS layer. So the phys are initialized +and updated by the LLDD and the ports are initialized and +updated by the SAS layer. + +There is a scheme where the LLDD can RW certain fields, +and the SAS layer can only read such ones, and vice versa. +The idea is to avoid unnecessary locking. + +enabled + - must be set (0/1) + +id + - must be set [0,MAX_PHYS)] + +class, proto, type, role, oob_mode, linkrate + - must be set + +oob_mode + - you set this when OOB has finished and then notify + the SAS Layer. + +sas_addr + - this normally points to an array holding the sas + address of the phy, possibly somewhere in your my_phy + struct. + +attached_sas_addr + - set this when you (LLDD) receive an + IDENTIFY frame or a FIS frame, _before_ notifying the SAS + layer. The idea is that sometimes the LLDD may want to fake + or provide a different SAS address on that phy/port and this + allows it to do this. At best you should copy the sas + address from the IDENTIFY frame or maybe generate a SAS + address for SATA directly attached devices. The Discover + process may later change this. + +frame_rcvd + - this is where you copy the IDENTIFY/FIS frame + when you get it; you lock, copy, set frame_rcvd_size and + unlock the lock, and then call the event. It is a pointer + since there's no way to know your hw frame size _exactly_, + so you define the actual array in your phy struct and let + this pointer point to it. You copy the frame from your + DMAable memory to that area holding the lock. + +sas_prim + - this is where primitives go when they're + received. See sas.h. Grab the lock, set the primitive, + release the lock, notify. + +port + - this points to the sas_port if the phy belongs + to a port -- the LLDD only reads this. It points to the + sas_port this phy is part of. Set by the SAS Layer. + +ha + - may be set; the SAS layer sets it anyway. + +lldd_phy + - you should set this to point to your phy so you + can find your way around faster when the SAS layer calls one + of your callbacks and passes you a phy. If the sas_phy is + embedded you can also use container_of -- whatever you + prefer. + + +``struct sas_port`` +------------------- + +The LLDD doesn't set any fields of this struct -- it only +reads them. They should be self explanatory. + +phy_mask is 32 bit, this should be enough for now, as I +haven't heard of a HA having more than 8 phys. + +lldd_port + - I haven't found use for that -- maybe other + LLDD who wish to have internal port representation can make + use of this. + +``struct sas_ha_struct`` +------------------------ + +It normally is statically declared in your own LLDD +structure describing your adapter:: + + struct my_sas_ha { + blah; + struct sas_ha_struct sas_ha; + struct my_phy phys[MAX_PHYS]; + struct sas_port sas_ports[MAX_PHYS]; /* (1) */ + bleh; + }; + + (1) If your LLDD doesn't have its own port representation. + +What needs to be initialized (sample function given below). + +pcidev +^^^^^^ + +sas_addr + - since the SAS layer doesn't want to mess with + memory allocation, etc, this points to statically + allocated array somewhere (say in your host adapter + structure) and holds the SAS address of the host + adapter as given by you or the manufacturer, etc. + +sas_port +^^^^^^^^ + +sas_phy + - an array of pointers to structures. (see + note above on sas_addr). + These must be set. See more notes below. + +num_phys + - the number of phys present in the sas_phy array, + and the number of ports present in the sas_port + array. There can be a maximum num_phys ports (one per + port) so we drop the num_ports, and only use + num_phys. + +The event interface:: + + /* LLDD calls these to notify the class of an event. */ + void sas_notify_port_event(struct sas_phy *, enum port_event); + void sas_notify_phy_event(struct sas_phy *, enum phy_event); + void sas_notify_port_event_gfp(struct sas_phy *, enum port_event, gfp_t); + void sas_notify_phy_event_gfp(struct sas_phy *, enum phy_event, gfp_t); + +The port notification:: + + /* The class calls these to notify the LLDD of an event. */ + void (*lldd_port_formed)(struct sas_phy *); + void (*lldd_port_deformed)(struct sas_phy *); + +If the LLDD wants notification when a port has been formed +or deformed it sets those to a function satisfying the type. + +A SAS LLDD should also implement at least one of the Task +Management Functions (TMFs) described in SAM:: + + /* Task Management Functions. Must be called from process context. */ + int (*lldd_abort_task)(struct sas_task *); + int (*lldd_abort_task_set)(struct domain_device *, u8 *lun); + int (*lldd_clear_aca)(struct domain_device *, u8 *lun); + int (*lldd_clear_task_set)(struct domain_device *, u8 *lun); + int (*lldd_I_T_nexus_reset)(struct domain_device *); + int (*lldd_lu_reset)(struct domain_device *, u8 *lun); + int (*lldd_query_task)(struct sas_task *); + +For more information please read SAM from T10.org. + +Port and Adapter management:: + + /* Port and Adapter management */ + int (*lldd_clear_nexus_port)(struct sas_port *); + int (*lldd_clear_nexus_ha)(struct sas_ha_struct *); + +A SAS LLDD should implement at least one of those. + +Phy management:: + + /* Phy management */ + int (*lldd_control_phy)(struct sas_phy *, enum phy_func); + +lldd_ha + - set this to point to your HA struct. You can also + use container_of if you embedded it as shown above. + +A sample initialization and registration function +can look like this (called last thing from probe()) +*but* before you enable the phys to do OOB:: + + static int register_sas_ha(struct my_sas_ha *my_ha) + { + int i; + static struct sas_phy *sas_phys[MAX_PHYS]; + static struct sas_port *sas_ports[MAX_PHYS]; + + my_ha->sas_ha.sas_addr = &my_ha->sas_addr[0]; + + for (i = 0; i < MAX_PHYS; i++) { + sas_phys[i] = &my_ha->phys[i].sas_phy; + sas_ports[i] = &my_ha->sas_ports[i]; + } + + my_ha->sas_ha.sas_phy = sas_phys; + my_ha->sas_ha.sas_port = sas_ports; + my_ha->sas_ha.num_phys = MAX_PHYS; + + my_ha->sas_ha.lldd_port_formed = my_port_formed; + + my_ha->sas_ha.lldd_dev_found = my_dev_found; + my_ha->sas_ha.lldd_dev_gone = my_dev_gone; + + my_ha->sas_ha.lldd_execute_task = my_execute_task; + + my_ha->sas_ha.lldd_abort_task = my_abort_task; + my_ha->sas_ha.lldd_abort_task_set = my_abort_task_set; + my_ha->sas_ha.lldd_clear_aca = my_clear_aca; + my_ha->sas_ha.lldd_clear_task_set = my_clear_task_set; + my_ha->sas_ha.lldd_I_T_nexus_reset= NULL; (2) + my_ha->sas_ha.lldd_lu_reset = my_lu_reset; + my_ha->sas_ha.lldd_query_task = my_query_task; + + my_ha->sas_ha.lldd_clear_nexus_port = my_clear_nexus_port; + my_ha->sas_ha.lldd_clear_nexus_ha = my_clear_nexus_ha; + + my_ha->sas_ha.lldd_control_phy = my_control_phy; + + return sas_register_ha(&my_ha->sas_ha); + } + +(2) SAS 1.1 does not define I_T Nexus Reset TMF. + +Events +====== + +Events are **the only way** a SAS LLDD notifies the SAS layer +of anything. There is no other method or way a LLDD to tell +the SAS layer of anything happening internally or in the SAS +domain. + +Phy events:: + + PHYE_LOSS_OF_SIGNAL, (C) + PHYE_OOB_DONE, + PHYE_OOB_ERROR, (C) + PHYE_SPINUP_HOLD. + +Port events, passed on a _phy_:: + + PORTE_BYTES_DMAED, (M) + PORTE_BROADCAST_RCVD, (E) + PORTE_LINK_RESET_ERR, (C) + PORTE_TIMER_EVENT, (C) + PORTE_HARD_RESET. + +Host Adapter event: + HAE_RESET + +A SAS LLDD should be able to generate + + - at least one event from group C (choice), + - events marked M (mandatory) are mandatory (only one), + - events marked E (expander) if it wants the SAS layer + to handle domain revalidation (only one such). + - Unmarked events are optional. + +Meaning: + +HAE_RESET + - when your HA got internal error and was reset. + +PORTE_BYTES_DMAED + - on receiving an IDENTIFY/FIS frame + +PORTE_BROADCAST_RCVD + - on receiving a primitive + +PORTE_LINK_RESET_ERR + - timer expired, loss of signal, loss of DWS, etc. [1]_ + +PORTE_TIMER_EVENT + - DWS reset timeout timer expired [1]_ + +PORTE_HARD_RESET + - Hard Reset primitive received. + +PHYE_LOSS_OF_SIGNAL + - the device is gone [1]_ + +PHYE_OOB_DONE + - OOB went fine and oob_mode is valid + +PHYE_OOB_ERROR + - Error while doing OOB, the device probably + got disconnected. [1]_ + +PHYE_SPINUP_HOLD + - SATA is present, COMWAKE not sent. + +.. [1] should set/clear the appropriate fields in the phy, + or alternatively call the inlined sas_phy_disconnected() + which is just a helper, from their tasklet. + +The Execute Command SCSI RPC:: + + int (*lldd_execute_task)(struct sas_task *, gfp_t gfp_flags); + +Used to queue a task to the SAS LLDD. @task is the task to be executed. +@gfp_mask is the gfp_mask defining the context of the caller. + +This function should implement the Execute Command SCSI RPC, + +That is, when lldd_execute_task() is called, the command +go out on the transport *immediately*. There is *no* +queuing of any sort and at any level in a SAS LLDD. + +Returns: + + * -SAS_QUEUE_FULL, -ENOMEM, nothing was queued; + * 0, the task(s) were queued. + +:: + + struct sas_task { + dev -- the device this task is destined to + task_proto -- _one_ of enum sas_proto + scatter -- pointer to scatter gather list array + num_scatter -- number of elements in scatter + total_xfer_len -- total number of bytes expected to be transferred + data_dir -- PCI_DMA_... + task_done -- callback when the task has finished execution + }; + +Discovery +========= + +The sysfs tree has the following purposes: + + a) It shows you the physical layout of the SAS domain at + the current time, i.e. how the domain looks in the + physical world right now. + b) Shows some device parameters _at_discovery_time_. + +This is a link to the tree(1) program, very useful in +viewing the SAS domain: +ftp://mama.indstate.edu/linux/tree/ + +I expect user space applications to actually create a +graphical interface of this. + +That is, the sysfs domain tree doesn't show or keep state if +you e.g., change the meaning of the READY LED MEANING +setting, but it does show you the current connection status +of the domain device. + +Keeping internal device state changes is responsibility of +upper layers (Command set drivers) and user space. + +When a device or devices are unplugged from the domain, this +is reflected in the sysfs tree immediately, and the device(s) +removed from the system. + +The structure domain_device describes any device in the SAS +domain. It is completely managed by the SAS layer. A task +points to a domain device, this is how the SAS LLDD knows +where to send the task(s) to. A SAS LLDD only reads the +contents of the domain_device structure, but it never creates +or destroys one. + +Expander management from User Space +=================================== + +In each expander directory in sysfs, there is a file called +"smp_portal". It is a binary sysfs attribute file, which +implements an SMP portal (Note: this is *NOT* an SMP port), +to which user space applications can send SMP requests and +receive SMP responses. + +Functionality is deceptively simple: + +1. Build the SMP frame you want to send. The format and layout + is described in the SAS spec. Leave the CRC field equal 0. + +open(2) + +2. Open the expander's SMP portal sysfs file in RW mode. + +write(2) + +3. Write the frame you built in 1. + +read(2) + +4. Read the amount of data you expect to receive for the frame you built. + If you receive different amount of data you expected to receive, + then there was some kind of error. + +close(2) + +All this process is shown in detail in the function do_smp_func() +and its callers, in the file "expander_conf.c". + +The kernel functionality is implemented in the file +"sas_expander.c". + +The program "expander_conf.c" implements this. It takes one +argument, the sysfs file name of the SMP portal to the +expander, and gives expander information, including routing +tables. + +The SMP portal gives you complete control of the expander, +so please be careful. |