| .. 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, gfp_t); |
| void sas_notify_phy_event(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_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_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. |