| 
									
										
										
										
											2013-08-23 10:35:45 -04:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  *  linux/drivers/scsi/esas2r/esas2r_ioctl.c | 
					
						
							|  |  |  |  *      For use with ATTO ExpressSAS R6xx SAS/SATA RAID controllers | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *  Copyright (c) 2001-2013 ATTO Technology, Inc. | 
					
						
							|  |  |  |  *  (mailto:linuxdrivers@attotech.com) | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This program is free software; you can redistribute it and/or | 
					
						
							|  |  |  |  * modify it under the terms of the GNU General Public License | 
					
						
							|  |  |  |  * as published by the Free Software Foundation; either version 2 | 
					
						
							|  |  |  |  * of the License, or (at your option) any later version. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This program is distributed in the hope that it will be useful, | 
					
						
							|  |  |  |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
					
						
							|  |  |  |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
					
						
							|  |  |  |  * GNU General Public License for more details. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * NO WARRANTY | 
					
						
							|  |  |  |  * THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR | 
					
						
							|  |  |  |  * CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT | 
					
						
							|  |  |  |  * LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, | 
					
						
							|  |  |  |  * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is | 
					
						
							|  |  |  |  * solely responsible for determining the appropriateness of using and | 
					
						
							|  |  |  |  * distributing the Program and assumes all risks associated with its | 
					
						
							|  |  |  |  * exercise of rights under this Agreement, including but not limited to | 
					
						
							|  |  |  |  * the risks and costs of program errors, damage to or loss of data, | 
					
						
							|  |  |  |  * programs or equipment, and unavailability or interruption of operations. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * DISCLAIMER OF LIABILITY | 
					
						
							|  |  |  |  * NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY | 
					
						
							|  |  |  |  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | 
					
						
							|  |  |  |  * DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND | 
					
						
							|  |  |  |  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR | 
					
						
							|  |  |  |  * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE | 
					
						
							|  |  |  |  * USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED | 
					
						
							|  |  |  |  * HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * You should have received a copy of the GNU General Public License | 
					
						
							|  |  |  |  * along with this program; if not, write to the Free Software | 
					
						
							|  |  |  |  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, | 
					
						
							|  |  |  |  * USA. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "esas2r.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * Buffered ioctl handlers.  A buffered ioctl is one which requires that we | 
					
						
							|  |  |  |  * allocate a DMA-able memory area to communicate with the firmware.  In | 
					
						
							|  |  |  |  * order to prevent continually allocating and freeing consistent memory, | 
					
						
							|  |  |  |  * we will allocate a global buffer the first time we need it and re-use | 
					
						
							|  |  |  |  * it for subsequent ioctl calls that require it. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | u8 *esas2r_buffered_ioctl; | 
					
						
							|  |  |  | dma_addr_t esas2r_buffered_ioctl_addr; | 
					
						
							|  |  |  | u32 esas2r_buffered_ioctl_size; | 
					
						
							|  |  |  | struct pci_dev *esas2r_buffered_ioctl_pcid; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static DEFINE_SEMAPHORE(buffered_ioctl_semaphore); | 
					
						
							|  |  |  | typedef int (*BUFFERED_IOCTL_CALLBACK)(struct esas2r_adapter *, | 
					
						
							|  |  |  | 				       struct esas2r_request *, | 
					
						
							|  |  |  | 				       struct esas2r_sg_context *, | 
					
						
							|  |  |  | 				       void *); | 
					
						
							|  |  |  | typedef void (*BUFFERED_IOCTL_DONE_CALLBACK)(struct esas2r_adapter *, | 
					
						
							|  |  |  | 					     struct esas2r_request *, void *); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | struct esas2r_buffered_ioctl { | 
					
						
							|  |  |  | 	struct esas2r_adapter *a; | 
					
						
							|  |  |  | 	void *ioctl; | 
					
						
							|  |  |  | 	u32 length; | 
					
						
							|  |  |  | 	u32 control_code; | 
					
						
							|  |  |  | 	u32 offset; | 
					
						
							|  |  |  | 	BUFFERED_IOCTL_CALLBACK | 
					
						
							|  |  |  | 		callback; | 
					
						
							|  |  |  | 	void *context; | 
					
						
							|  |  |  | 	BUFFERED_IOCTL_DONE_CALLBACK | 
					
						
							|  |  |  | 		done_callback; | 
					
						
							|  |  |  | 	void *done_context; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void complete_fm_api_req(struct esas2r_adapter *a, | 
					
						
							|  |  |  | 				struct esas2r_request *rq) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	a->fm_api_command_done = 1; | 
					
						
							|  |  |  | 	wake_up_interruptible(&a->fm_api_waiter); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Callbacks for building scatter/gather lists for FM API requests */ | 
					
						
							|  |  |  | static u32 get_physaddr_fm_api(struct esas2r_sg_context *sgc, u64 *addr) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct esas2r_adapter *a = (struct esas2r_adapter *)sgc->adapter; | 
					
						
							|  |  |  | 	int offset = sgc->cur_offset - a->save_offset; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	(*addr) = a->firmware.phys + offset; | 
					
						
							|  |  |  | 	return a->firmware.orig_len - offset; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static u32 get_physaddr_fm_api_header(struct esas2r_sg_context *sgc, u64 *addr) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct esas2r_adapter *a = (struct esas2r_adapter *)sgc->adapter; | 
					
						
							|  |  |  | 	int offset = sgc->cur_offset - a->save_offset; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	(*addr) = a->firmware.header_buff_phys + offset; | 
					
						
							|  |  |  | 	return sizeof(struct esas2r_flash_img) - offset; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Handle EXPRESS_IOCTL_RW_FIRMWARE ioctl with img_type = FW_IMG_FM_API. */ | 
					
						
							|  |  |  | static void do_fm_api(struct esas2r_adapter *a, struct esas2r_flash_img *fi) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct esas2r_request *rq; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (down_interruptible(&a->fm_api_semaphore)) { | 
					
						
							|  |  |  | 		fi->status = FI_STAT_BUSY; | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	rq = esas2r_alloc_request(a); | 
					
						
							|  |  |  | 	if (rq == NULL) { | 
					
						
							|  |  |  | 		up(&a->fm_api_semaphore); | 
					
						
							|  |  |  | 		fi->status = FI_STAT_BUSY; | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (fi == &a->firmware.header) { | 
					
						
							|  |  |  | 		a->firmware.header_buff = dma_alloc_coherent(&a->pcid->dev, | 
					
						
							|  |  |  | 							     (size_t)sizeof( | 
					
						
							|  |  |  | 								     struct | 
					
						
							|  |  |  | 								     esas2r_flash_img), | 
					
						
							|  |  |  | 							     (dma_addr_t *)&a-> | 
					
						
							|  |  |  | 							     firmware. | 
					
						
							|  |  |  | 							     header_buff_phys, | 
					
						
							|  |  |  | 							     GFP_KERNEL); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (a->firmware.header_buff == NULL) { | 
					
						
							|  |  |  | 			esas2r_debug("failed to allocate header buffer!"); | 
					
						
							|  |  |  | 			fi->status = FI_STAT_BUSY; | 
					
						
							|  |  |  | 			return; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		memcpy(a->firmware.header_buff, fi, | 
					
						
							|  |  |  | 		       sizeof(struct esas2r_flash_img)); | 
					
						
							|  |  |  | 		a->save_offset = a->firmware.header_buff; | 
					
						
							|  |  |  | 		a->fm_api_sgc.get_phys_addr = | 
					
						
							|  |  |  | 			(PGETPHYSADDR)get_physaddr_fm_api_header; | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		a->save_offset = (u8 *)fi; | 
					
						
							|  |  |  | 		a->fm_api_sgc.get_phys_addr = | 
					
						
							|  |  |  | 			(PGETPHYSADDR)get_physaddr_fm_api; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	rq->comp_cb = complete_fm_api_req; | 
					
						
							|  |  |  | 	a->fm_api_command_done = 0; | 
					
						
							|  |  |  | 	a->fm_api_sgc.cur_offset = a->save_offset; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!esas2r_fm_api(a, (struct esas2r_flash_img *)a->save_offset, rq, | 
					
						
							|  |  |  | 			   &a->fm_api_sgc)) | 
					
						
							|  |  |  | 		goto all_done; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Now wait around for it to complete. */ | 
					
						
							|  |  |  | 	while (!a->fm_api_command_done) | 
					
						
							|  |  |  | 		wait_event_interruptible(a->fm_api_waiter, | 
					
						
							|  |  |  | 					 a->fm_api_command_done); | 
					
						
							|  |  |  | all_done: | 
					
						
							|  |  |  | 	if (fi == &a->firmware.header) { | 
					
						
							|  |  |  | 		memcpy(fi, a->firmware.header_buff, | 
					
						
							|  |  |  | 		       sizeof(struct esas2r_flash_img)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		dma_free_coherent(&a->pcid->dev, | 
					
						
							|  |  |  | 				  (size_t)sizeof(struct esas2r_flash_img), | 
					
						
							|  |  |  | 				  a->firmware.header_buff, | 
					
						
							|  |  |  | 				  (dma_addr_t)a->firmware.header_buff_phys); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	up(&a->fm_api_semaphore); | 
					
						
							|  |  |  | 	esas2r_free_request(a, (struct esas2r_request *)rq); | 
					
						
							|  |  |  | 	return; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void complete_nvr_req(struct esas2r_adapter *a, | 
					
						
							|  |  |  | 			     struct esas2r_request *rq) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	a->nvram_command_done = 1; | 
					
						
							|  |  |  | 	wake_up_interruptible(&a->nvram_waiter); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Callback for building scatter/gather lists for buffered ioctls */ | 
					
						
							|  |  |  | static u32 get_physaddr_buffered_ioctl(struct esas2r_sg_context *sgc, | 
					
						
							|  |  |  | 				       u64 *addr) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int offset = (u8 *)sgc->cur_offset - esas2r_buffered_ioctl; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	(*addr) = esas2r_buffered_ioctl_addr + offset; | 
					
						
							|  |  |  | 	return esas2r_buffered_ioctl_size - offset; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void complete_buffered_ioctl_req(struct esas2r_adapter *a, | 
					
						
							|  |  |  | 					struct esas2r_request *rq) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	a->buffered_ioctl_done = 1; | 
					
						
							|  |  |  | 	wake_up_interruptible(&a->buffered_ioctl_waiter); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static u8 handle_buffered_ioctl(struct esas2r_buffered_ioctl *bi) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct esas2r_adapter *a = bi->a; | 
					
						
							|  |  |  | 	struct esas2r_request *rq; | 
					
						
							|  |  |  | 	struct esas2r_sg_context sgc; | 
					
						
							|  |  |  | 	u8 result = IOCTL_SUCCESS; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (down_interruptible(&buffered_ioctl_semaphore)) | 
					
						
							|  |  |  | 		return IOCTL_OUT_OF_RESOURCES; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* allocate a buffer or use the existing buffer. */ | 
					
						
							|  |  |  | 	if (esas2r_buffered_ioctl) { | 
					
						
							|  |  |  | 		if (esas2r_buffered_ioctl_size < bi->length) { | 
					
						
							|  |  |  | 			/* free the too-small buffer and get a new one */ | 
					
						
							|  |  |  | 			dma_free_coherent(&a->pcid->dev, | 
					
						
							|  |  |  | 					  (size_t)esas2r_buffered_ioctl_size, | 
					
						
							|  |  |  | 					  esas2r_buffered_ioctl, | 
					
						
							|  |  |  | 					  esas2r_buffered_ioctl_addr); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			goto allocate_buffer; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | allocate_buffer: | 
					
						
							|  |  |  | 		esas2r_buffered_ioctl_size = bi->length; | 
					
						
							|  |  |  | 		esas2r_buffered_ioctl_pcid = a->pcid; | 
					
						
							|  |  |  | 		esas2r_buffered_ioctl = dma_alloc_coherent(&a->pcid->dev, | 
					
						
							|  |  |  | 							   (size_t) | 
					
						
							|  |  |  | 							   esas2r_buffered_ioctl_size, | 
					
						
							|  |  |  | 							   & | 
					
						
							|  |  |  | 							   esas2r_buffered_ioctl_addr, | 
					
						
							|  |  |  | 							   GFP_KERNEL); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!esas2r_buffered_ioctl) { | 
					
						
							|  |  |  | 		esas2r_log(ESAS2R_LOG_CRIT, | 
					
						
							|  |  |  | 			   "could not allocate %d bytes of consistent memory " | 
					
						
							|  |  |  | 			   "for a buffered ioctl!", | 
					
						
							|  |  |  | 			   bi->length); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		esas2r_debug("buffered ioctl alloc failure"); | 
					
						
							|  |  |  | 		result = IOCTL_OUT_OF_RESOURCES; | 
					
						
							|  |  |  | 		goto exit_cleanly; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	memcpy(esas2r_buffered_ioctl, bi->ioctl, bi->length); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	rq = esas2r_alloc_request(a); | 
					
						
							|  |  |  | 	if (rq == NULL) { | 
					
						
							|  |  |  | 		esas2r_log(ESAS2R_LOG_CRIT, | 
					
						
							|  |  |  | 			   "could not allocate an internal request"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		result = IOCTL_OUT_OF_RESOURCES; | 
					
						
							|  |  |  | 		esas2r_debug("buffered ioctl - no requests"); | 
					
						
							|  |  |  | 		goto exit_cleanly; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	a->buffered_ioctl_done = 0; | 
					
						
							|  |  |  | 	rq->comp_cb = complete_buffered_ioctl_req; | 
					
						
							|  |  |  | 	sgc.cur_offset = esas2r_buffered_ioctl + bi->offset; | 
					
						
							|  |  |  | 	sgc.get_phys_addr = (PGETPHYSADDR)get_physaddr_buffered_ioctl; | 
					
						
							|  |  |  | 	sgc.length = esas2r_buffered_ioctl_size; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!(*bi->callback)(a, rq, &sgc, bi->context)) { | 
					
						
							|  |  |  | 		/* completed immediately, no need to wait */ | 
					
						
							|  |  |  | 		a->buffered_ioctl_done = 0; | 
					
						
							|  |  |  | 		goto free_andexit_cleanly; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* now wait around for it to complete. */ | 
					
						
							|  |  |  | 	while (!a->buffered_ioctl_done) | 
					
						
							|  |  |  | 		wait_event_interruptible(a->buffered_ioctl_waiter, | 
					
						
							|  |  |  | 					 a->buffered_ioctl_done); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | free_andexit_cleanly: | 
					
						
							|  |  |  | 	if (result == IOCTL_SUCCESS && bi->done_callback) | 
					
						
							|  |  |  | 		(*bi->done_callback)(a, rq, bi->done_context); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	esas2r_free_request(a, rq); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | exit_cleanly: | 
					
						
							|  |  |  | 	if (result == IOCTL_SUCCESS) | 
					
						
							|  |  |  | 		memcpy(bi->ioctl, esas2r_buffered_ioctl, bi->length); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	up(&buffered_ioctl_semaphore); | 
					
						
							|  |  |  | 	return result; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* SMP ioctl support */ | 
					
						
							|  |  |  | static int smp_ioctl_callback(struct esas2r_adapter *a, | 
					
						
							|  |  |  | 			      struct esas2r_request *rq, | 
					
						
							|  |  |  | 			      struct esas2r_sg_context *sgc, void *context) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct atto_ioctl_smp *si = | 
					
						
							|  |  |  | 		(struct atto_ioctl_smp *)esas2r_buffered_ioctl; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	esas2r_sgc_init(sgc, a, rq, rq->vrq->ioctl.sge); | 
					
						
							|  |  |  | 	esas2r_build_ioctl_req(a, rq, sgc->length, VDA_IOCTL_SMP); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!esas2r_build_sg_list(a, rq, sgc)) { | 
					
						
							|  |  |  | 		si->status = ATTO_STS_OUT_OF_RSRC; | 
					
						
							|  |  |  | 		return false; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	esas2r_start_request(a, rq); | 
					
						
							|  |  |  | 	return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static u8 handle_smp_ioctl(struct esas2r_adapter *a, struct atto_ioctl_smp *si) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct esas2r_buffered_ioctl bi; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	memset(&bi, 0, sizeof(bi)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	bi.a = a; | 
					
						
							|  |  |  | 	bi.ioctl = si; | 
					
						
							|  |  |  | 	bi.length = sizeof(struct atto_ioctl_smp) | 
					
						
							|  |  |  | 		    + le32_to_cpu(si->req_length) | 
					
						
							|  |  |  | 		    + le32_to_cpu(si->rsp_length); | 
					
						
							|  |  |  | 	bi.offset = 0; | 
					
						
							|  |  |  | 	bi.callback = smp_ioctl_callback; | 
					
						
							|  |  |  | 	return handle_buffered_ioctl(&bi); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* CSMI ioctl support */ | 
					
						
							|  |  |  | static void esas2r_csmi_ioctl_tunnel_comp_cb(struct esas2r_adapter *a, | 
					
						
							|  |  |  | 					     struct esas2r_request *rq) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	rq->target_id = le16_to_cpu(rq->func_rsp.ioctl_rsp.csmi.target_id); | 
					
						
							|  |  |  | 	rq->vrq->scsi.flags |= cpu_to_le32(rq->func_rsp.ioctl_rsp.csmi.lun); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Now call the original completion callback. */ | 
					
						
							|  |  |  | 	(*rq->aux_req_cb)(a, rq); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Tunnel a CSMI IOCTL to the back end driver for processing. */ | 
					
						
							|  |  |  | static bool csmi_ioctl_tunnel(struct esas2r_adapter *a, | 
					
						
							|  |  |  | 			      union atto_ioctl_csmi *ci, | 
					
						
							|  |  |  | 			      struct esas2r_request *rq, | 
					
						
							|  |  |  | 			      struct esas2r_sg_context *sgc, | 
					
						
							|  |  |  | 			      u32 ctrl_code, | 
					
						
							|  |  |  | 			      u16 target_id) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct atto_vda_ioctl_req *ioctl = &rq->vrq->ioctl; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-10-01 14:26:01 -04:00
										 |  |  | 	if (test_bit(AF_DEGRADED_MODE, &a->flags)) | 
					
						
							| 
									
										
										
										
											2013-08-23 10:35:45 -04:00
										 |  |  | 		return false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	esas2r_sgc_init(sgc, a, rq, rq->vrq->ioctl.sge); | 
					
						
							|  |  |  | 	esas2r_build_ioctl_req(a, rq, sgc->length, VDA_IOCTL_CSMI); | 
					
						
							|  |  |  | 	ioctl->csmi.ctrl_code = cpu_to_le32(ctrl_code); | 
					
						
							|  |  |  | 	ioctl->csmi.target_id = cpu_to_le16(target_id); | 
					
						
							|  |  |  | 	ioctl->csmi.lun = (u8)le32_to_cpu(rq->vrq->scsi.flags); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/*
 | 
					
						
							|  |  |  | 	 * Always usurp the completion callback since the interrupt callback | 
					
						
							|  |  |  | 	 * mechanism may be used. | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	rq->aux_req_cx = ci; | 
					
						
							|  |  |  | 	rq->aux_req_cb = rq->comp_cb; | 
					
						
							|  |  |  | 	rq->comp_cb = esas2r_csmi_ioctl_tunnel_comp_cb; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!esas2r_build_sg_list(a, rq, sgc)) | 
					
						
							|  |  |  | 		return false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	esas2r_start_request(a, rq); | 
					
						
							|  |  |  | 	return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static bool check_lun(struct scsi_lun lun) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	bool result; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	result = ((lun.scsi_lun[7] == 0) && | 
					
						
							|  |  |  | 		  (lun.scsi_lun[6] == 0) && | 
					
						
							|  |  |  | 		  (lun.scsi_lun[5] == 0) && | 
					
						
							|  |  |  | 		  (lun.scsi_lun[4] == 0) && | 
					
						
							|  |  |  | 		  (lun.scsi_lun[3] == 0) && | 
					
						
							|  |  |  | 		  (lun.scsi_lun[2] == 0) && | 
					
						
							|  |  |  | /* Byte 1 is intentionally skipped */ | 
					
						
							|  |  |  | 		  (lun.scsi_lun[0] == 0)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return result; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int csmi_ioctl_callback(struct esas2r_adapter *a, | 
					
						
							|  |  |  | 			       struct esas2r_request *rq, | 
					
						
							|  |  |  | 			       struct esas2r_sg_context *sgc, void *context) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct atto_csmi *ci = (struct atto_csmi *)context; | 
					
						
							|  |  |  | 	union atto_ioctl_csmi *ioctl_csmi = | 
					
						
							|  |  |  | 		(union atto_ioctl_csmi *)esas2r_buffered_ioctl; | 
					
						
							|  |  |  | 	u8 path = 0; | 
					
						
							|  |  |  | 	u8 tid = 0; | 
					
						
							|  |  |  | 	u8 lun = 0; | 
					
						
							|  |  |  | 	u32 sts = CSMI_STS_SUCCESS; | 
					
						
							|  |  |  | 	struct esas2r_target *t; | 
					
						
							|  |  |  | 	unsigned long flags; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (ci->control_code == CSMI_CC_GET_DEV_ADDR) { | 
					
						
							|  |  |  | 		struct atto_csmi_get_dev_addr *gda = &ci->data.dev_addr; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		path = gda->path_id; | 
					
						
							|  |  |  | 		tid = gda->target_id; | 
					
						
							|  |  |  | 		lun = gda->lun; | 
					
						
							|  |  |  | 	} else if (ci->control_code == CSMI_CC_TASK_MGT) { | 
					
						
							|  |  |  | 		struct atto_csmi_task_mgmt *tm = &ci->data.tsk_mgt; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		path = tm->path_id; | 
					
						
							|  |  |  | 		tid = tm->target_id; | 
					
						
							|  |  |  | 		lun = tm->lun; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-08-29 15:55:43 -04:00
										 |  |  | 	if (path > 0) { | 
					
						
							| 
									
										
										
										
											2013-08-23 10:35:45 -04:00
										 |  |  | 		rq->func_rsp.ioctl_rsp.csmi.csmi_status = cpu_to_le32( | 
					
						
							|  |  |  | 			CSMI_STS_INV_PARAM); | 
					
						
							|  |  |  | 		return false; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	rq->target_id = tid; | 
					
						
							|  |  |  | 	rq->vrq->scsi.flags |= cpu_to_le32(lun); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch (ci->control_code) { | 
					
						
							|  |  |  | 	case CSMI_CC_GET_DRVR_INFO: | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		struct atto_csmi_get_driver_info *gdi = &ioctl_csmi->drvr_info; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		strcpy(gdi->description, esas2r_get_model_name(a)); | 
					
						
							|  |  |  | 		gdi->csmi_major_rev = CSMI_MAJOR_REV; | 
					
						
							|  |  |  | 		gdi->csmi_minor_rev = CSMI_MINOR_REV; | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case CSMI_CC_GET_CNTLR_CFG: | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		struct atto_csmi_get_cntlr_cfg *gcc = &ioctl_csmi->cntlr_cfg; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		gcc->base_io_addr = 0; | 
					
						
							|  |  |  | 		pci_read_config_dword(a->pcid, PCI_BASE_ADDRESS_2, | 
					
						
							|  |  |  | 				      &gcc->base_memaddr_lo); | 
					
						
							|  |  |  | 		pci_read_config_dword(a->pcid, PCI_BASE_ADDRESS_3, | 
					
						
							|  |  |  | 				      &gcc->base_memaddr_hi); | 
					
						
							|  |  |  | 		gcc->board_id = MAKEDWORD(a->pcid->subsystem_device, | 
					
						
							|  |  |  | 					  a->pcid->subsystem_vendor); | 
					
						
							|  |  |  | 		gcc->slot_num = CSMI_SLOT_NUM_UNKNOWN; | 
					
						
							|  |  |  | 		gcc->cntlr_class = CSMI_CNTLR_CLASS_HBA; | 
					
						
							|  |  |  | 		gcc->io_bus_type = CSMI_BUS_TYPE_PCI; | 
					
						
							|  |  |  | 		gcc->pci_addr.bus_num = a->pcid->bus->number; | 
					
						
							|  |  |  | 		gcc->pci_addr.device_num = PCI_SLOT(a->pcid->devfn); | 
					
						
							|  |  |  | 		gcc->pci_addr.function_num = PCI_FUNC(a->pcid->devfn); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		memset(gcc->serial_num, 0, sizeof(gcc->serial_num)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		gcc->major_rev = LOBYTE(LOWORD(a->fw_version)); | 
					
						
							|  |  |  | 		gcc->minor_rev = HIBYTE(LOWORD(a->fw_version)); | 
					
						
							|  |  |  | 		gcc->build_rev = LOBYTE(HIWORD(a->fw_version)); | 
					
						
							|  |  |  | 		gcc->release_rev = HIBYTE(HIWORD(a->fw_version)); | 
					
						
							|  |  |  | 		gcc->bios_major_rev = HIBYTE(HIWORD(a->flash_ver)); | 
					
						
							|  |  |  | 		gcc->bios_minor_rev = LOBYTE(HIWORD(a->flash_ver)); | 
					
						
							|  |  |  | 		gcc->bios_build_rev = LOWORD(a->flash_ver); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-10-01 14:26:01 -04:00
										 |  |  | 		if (test_bit(AF2_THUNDERLINK, &a->flags2)) | 
					
						
							| 
									
										
										
										
											2013-08-23 10:35:45 -04:00
										 |  |  | 			gcc->cntlr_flags = CSMI_CNTLRF_SAS_HBA | 
					
						
							|  |  |  | 					   | CSMI_CNTLRF_SATA_HBA; | 
					
						
							|  |  |  | 		else | 
					
						
							|  |  |  | 			gcc->cntlr_flags = CSMI_CNTLRF_SAS_RAID | 
					
						
							|  |  |  | 					   | CSMI_CNTLRF_SATA_RAID; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		gcc->rrom_major_rev = 0; | 
					
						
							|  |  |  | 		gcc->rrom_minor_rev = 0; | 
					
						
							|  |  |  | 		gcc->rrom_build_rev = 0; | 
					
						
							|  |  |  | 		gcc->rrom_release_rev = 0; | 
					
						
							|  |  |  | 		gcc->rrom_biosmajor_rev = 0; | 
					
						
							|  |  |  | 		gcc->rrom_biosminor_rev = 0; | 
					
						
							|  |  |  | 		gcc->rrom_biosbuild_rev = 0; | 
					
						
							|  |  |  | 		gcc->rrom_biosrelease_rev = 0; | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case CSMI_CC_GET_CNTLR_STS: | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		struct atto_csmi_get_cntlr_sts *gcs = &ioctl_csmi->cntlr_sts; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-10-01 14:26:01 -04:00
										 |  |  | 		if (test_bit(AF_DEGRADED_MODE, &a->flags)) | 
					
						
							| 
									
										
										
										
											2013-08-23 10:35:45 -04:00
										 |  |  | 			gcs->status = CSMI_CNTLR_STS_FAILED; | 
					
						
							|  |  |  | 		else | 
					
						
							|  |  |  | 			gcs->status = CSMI_CNTLR_STS_GOOD; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		gcs->offline_reason = CSMI_OFFLINE_NO_REASON; | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case CSMI_CC_FW_DOWNLOAD: | 
					
						
							|  |  |  | 	case CSMI_CC_GET_RAID_INFO: | 
					
						
							|  |  |  | 	case CSMI_CC_GET_RAID_CFG: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		sts = CSMI_STS_BAD_CTRL_CODE; | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case CSMI_CC_SMP_PASSTHRU: | 
					
						
							|  |  |  | 	case CSMI_CC_SSP_PASSTHRU: | 
					
						
							|  |  |  | 	case CSMI_CC_STP_PASSTHRU: | 
					
						
							|  |  |  | 	case CSMI_CC_GET_PHY_INFO: | 
					
						
							|  |  |  | 	case CSMI_CC_SET_PHY_INFO: | 
					
						
							|  |  |  | 	case CSMI_CC_GET_LINK_ERRORS: | 
					
						
							|  |  |  | 	case CSMI_CC_GET_SATA_SIG: | 
					
						
							|  |  |  | 	case CSMI_CC_GET_CONN_INFO: | 
					
						
							|  |  |  | 	case CSMI_CC_PHY_CTRL: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (!csmi_ioctl_tunnel(a, ioctl_csmi, rq, sgc, | 
					
						
							|  |  |  | 				       ci->control_code, | 
					
						
							|  |  |  | 				       ESAS2R_TARG_ID_INV)) { | 
					
						
							|  |  |  | 			sts = CSMI_STS_FAILED; | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return true; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case CSMI_CC_GET_SCSI_ADDR: | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		struct atto_csmi_get_scsi_addr *gsa = &ioctl_csmi->scsi_addr; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		struct scsi_lun lun; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		memcpy(&lun, gsa->sas_lun, sizeof(struct scsi_lun)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (!check_lun(lun)) { | 
					
						
							|  |  |  | 			sts = CSMI_STS_NO_SCSI_ADDR; | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* make sure the device is present */ | 
					
						
							|  |  |  | 		spin_lock_irqsave(&a->mem_lock, flags); | 
					
						
							|  |  |  | 		t = esas2r_targ_db_find_by_sas_addr(a, (u64 *)gsa->sas_addr); | 
					
						
							|  |  |  | 		spin_unlock_irqrestore(&a->mem_lock, flags); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (t == NULL) { | 
					
						
							|  |  |  | 			sts = CSMI_STS_NO_SCSI_ADDR; | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		gsa->host_index = 0xFF; | 
					
						
							|  |  |  | 		gsa->lun = gsa->sas_lun[1]; | 
					
						
							|  |  |  | 		rq->target_id = esas2r_targ_get_id(t, a); | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case CSMI_CC_GET_DEV_ADDR: | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		struct atto_csmi_get_dev_addr *gda = &ioctl_csmi->dev_addr; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* make sure the target is present */ | 
					
						
							|  |  |  | 		t = a->targetdb + rq->target_id; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (t >= a->targetdb_end | 
					
						
							|  |  |  | 		    || t->target_state != TS_PRESENT | 
					
						
							|  |  |  | 		    || t->sas_addr == 0) { | 
					
						
							|  |  |  | 			sts = CSMI_STS_NO_DEV_ADDR; | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* fill in the result */ | 
					
						
							|  |  |  | 		*(u64 *)gda->sas_addr = t->sas_addr; | 
					
						
							|  |  |  | 		memset(gda->sas_lun, 0, sizeof(gda->sas_lun)); | 
					
						
							|  |  |  | 		gda->sas_lun[1] = (u8)le32_to_cpu(rq->vrq->scsi.flags); | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case CSMI_CC_TASK_MGT: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* make sure the target is present */ | 
					
						
							|  |  |  | 		t = a->targetdb + rq->target_id; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (t >= a->targetdb_end | 
					
						
							|  |  |  | 		    || t->target_state != TS_PRESENT | 
					
						
							|  |  |  | 		    || !(t->flags & TF_PASS_THRU)) { | 
					
						
							|  |  |  | 			sts = CSMI_STS_NO_DEV_ADDR; | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (!csmi_ioctl_tunnel(a, ioctl_csmi, rq, sgc, | 
					
						
							|  |  |  | 				       ci->control_code, | 
					
						
							|  |  |  | 				       t->phys_targ_id)) { | 
					
						
							|  |  |  | 			sts = CSMI_STS_FAILED; | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return true; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		sts = CSMI_STS_BAD_CTRL_CODE; | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	rq->func_rsp.ioctl_rsp.csmi.csmi_status = cpu_to_le32(sts); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return false; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void csmi_ioctl_done_callback(struct esas2r_adapter *a, | 
					
						
							|  |  |  | 				     struct esas2r_request *rq, void *context) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct atto_csmi *ci = (struct atto_csmi *)context; | 
					
						
							|  |  |  | 	union atto_ioctl_csmi *ioctl_csmi = | 
					
						
							|  |  |  | 		(union atto_ioctl_csmi *)esas2r_buffered_ioctl; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch (ci->control_code) { | 
					
						
							|  |  |  | 	case CSMI_CC_GET_DRVR_INFO: | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		struct atto_csmi_get_driver_info *gdi = | 
					
						
							|  |  |  | 			&ioctl_csmi->drvr_info; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		strcpy(gdi->name, ESAS2R_VERSION_STR); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		gdi->major_rev = ESAS2R_MAJOR_REV; | 
					
						
							|  |  |  | 		gdi->minor_rev = ESAS2R_MINOR_REV; | 
					
						
							|  |  |  | 		gdi->build_rev = 0; | 
					
						
							|  |  |  | 		gdi->release_rev = 0; | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case CSMI_CC_GET_SCSI_ADDR: | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		struct atto_csmi_get_scsi_addr *gsa = &ioctl_csmi->scsi_addr; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (le32_to_cpu(rq->func_rsp.ioctl_rsp.csmi.csmi_status) == | 
					
						
							|  |  |  | 		    CSMI_STS_SUCCESS) { | 
					
						
							|  |  |  | 			gsa->target_id = rq->target_id; | 
					
						
							|  |  |  | 			gsa->path_id = 0; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ci->status = le32_to_cpu(rq->func_rsp.ioctl_rsp.csmi.csmi_status); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static u8 handle_csmi_ioctl(struct esas2r_adapter *a, struct atto_csmi *ci) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct esas2r_buffered_ioctl bi; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	memset(&bi, 0, sizeof(bi)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	bi.a = a; | 
					
						
							|  |  |  | 	bi.ioctl = &ci->data; | 
					
						
							|  |  |  | 	bi.length = sizeof(union atto_ioctl_csmi); | 
					
						
							|  |  |  | 	bi.offset = 0; | 
					
						
							|  |  |  | 	bi.callback = csmi_ioctl_callback; | 
					
						
							|  |  |  | 	bi.context = ci; | 
					
						
							|  |  |  | 	bi.done_callback = csmi_ioctl_done_callback; | 
					
						
							|  |  |  | 	bi.done_context = ci; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return handle_buffered_ioctl(&bi); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* ATTO HBA ioctl support */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Tunnel an ATTO HBA IOCTL to the back end driver for processing. */ | 
					
						
							|  |  |  | static bool hba_ioctl_tunnel(struct esas2r_adapter *a, | 
					
						
							|  |  |  | 			     struct atto_ioctl *hi, | 
					
						
							|  |  |  | 			     struct esas2r_request *rq, | 
					
						
							|  |  |  | 			     struct esas2r_sg_context *sgc) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	esas2r_sgc_init(sgc, a, rq, rq->vrq->ioctl.sge); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	esas2r_build_ioctl_req(a, rq, sgc->length, VDA_IOCTL_HBA); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!esas2r_build_sg_list(a, rq, sgc)) { | 
					
						
							|  |  |  | 		hi->status = ATTO_STS_OUT_OF_RSRC; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return false; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	esas2r_start_request(a, rq); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void scsi_passthru_comp_cb(struct esas2r_adapter *a, | 
					
						
							|  |  |  | 				  struct esas2r_request *rq) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct atto_ioctl *hi = (struct atto_ioctl *)rq->aux_req_cx; | 
					
						
							|  |  |  | 	struct atto_hba_scsi_pass_thru *spt = &hi->data.scsi_pass_thru; | 
					
						
							|  |  |  | 	u8 sts = ATTO_SPT_RS_FAILED; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	spt->scsi_status = rq->func_rsp.scsi_rsp.scsi_stat; | 
					
						
							|  |  |  | 	spt->sense_length = rq->sense_len; | 
					
						
							|  |  |  | 	spt->residual_length = | 
					
						
							|  |  |  | 		le32_to_cpu(rq->func_rsp.scsi_rsp.residual_length); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch (rq->req_stat) { | 
					
						
							|  |  |  | 	case RS_SUCCESS: | 
					
						
							|  |  |  | 	case RS_SCSI_ERROR: | 
					
						
							|  |  |  | 		sts = ATTO_SPT_RS_SUCCESS; | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	case RS_UNDERRUN: | 
					
						
							|  |  |  | 		sts = ATTO_SPT_RS_UNDERRUN; | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	case RS_OVERRUN: | 
					
						
							|  |  |  | 		sts = ATTO_SPT_RS_OVERRUN; | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	case RS_SEL: | 
					
						
							|  |  |  | 	case RS_SEL2: | 
					
						
							|  |  |  | 		sts = ATTO_SPT_RS_NO_DEVICE; | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	case RS_NO_LUN: | 
					
						
							|  |  |  | 		sts = ATTO_SPT_RS_NO_LUN; | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	case RS_TIMEOUT: | 
					
						
							|  |  |  | 		sts = ATTO_SPT_RS_TIMEOUT; | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	case RS_DEGRADED: | 
					
						
							|  |  |  | 		sts = ATTO_SPT_RS_DEGRADED; | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	case RS_BUSY: | 
					
						
							|  |  |  | 		sts = ATTO_SPT_RS_BUSY; | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	case RS_ABORTED: | 
					
						
							|  |  |  | 		sts = ATTO_SPT_RS_ABORTED; | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	case RS_RESET: | 
					
						
							|  |  |  | 		sts = ATTO_SPT_RS_BUS_RESET; | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	spt->req_status = sts; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Update the target ID to the next one present. */ | 
					
						
							|  |  |  | 	spt->target_id = | 
					
						
							|  |  |  | 		esas2r_targ_db_find_next_present(a, (u16)spt->target_id); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Done, call the completion callback. */ | 
					
						
							|  |  |  | 	(*rq->aux_req_cb)(a, rq); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int hba_ioctl_callback(struct esas2r_adapter *a, | 
					
						
							|  |  |  | 			      struct esas2r_request *rq, | 
					
						
							|  |  |  | 			      struct esas2r_sg_context *sgc, | 
					
						
							|  |  |  | 			      void *context) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct atto_ioctl *hi = (struct atto_ioctl *)esas2r_buffered_ioctl; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	hi->status = ATTO_STS_SUCCESS; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch (hi->function) { | 
					
						
							|  |  |  | 	case ATTO_FUNC_GET_ADAP_INFO: | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		u8 *class_code = (u8 *)&a->pcid->class; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		struct atto_hba_get_adapter_info *gai = | 
					
						
							|  |  |  | 			&hi->data.get_adap_info; | 
					
						
							|  |  |  | 		int pcie_cap_reg; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (hi->flags & HBAF_TUNNEL) { | 
					
						
							|  |  |  | 			hi->status = ATTO_STS_UNSUPPORTED; | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (hi->version > ATTO_VER_GET_ADAP_INFO0) { | 
					
						
							|  |  |  | 			hi->status = ATTO_STS_INV_VERSION; | 
					
						
							|  |  |  | 			hi->version = ATTO_VER_GET_ADAP_INFO0; | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		memset(gai, 0, sizeof(*gai)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		gai->pci.vendor_id = a->pcid->vendor; | 
					
						
							|  |  |  | 		gai->pci.device_id = a->pcid->device; | 
					
						
							|  |  |  | 		gai->pci.ss_vendor_id = a->pcid->subsystem_vendor; | 
					
						
							|  |  |  | 		gai->pci.ss_device_id = a->pcid->subsystem_device; | 
					
						
							|  |  |  | 		gai->pci.class_code[0] = class_code[0]; | 
					
						
							|  |  |  | 		gai->pci.class_code[1] = class_code[1]; | 
					
						
							|  |  |  | 		gai->pci.class_code[2] = class_code[2]; | 
					
						
							|  |  |  | 		gai->pci.rev_id = a->pcid->revision; | 
					
						
							|  |  |  | 		gai->pci.bus_num = a->pcid->bus->number; | 
					
						
							|  |  |  | 		gai->pci.dev_num = PCI_SLOT(a->pcid->devfn); | 
					
						
							|  |  |  | 		gai->pci.func_num = PCI_FUNC(a->pcid->devfn); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		pcie_cap_reg = pci_find_capability(a->pcid, PCI_CAP_ID_EXP); | 
					
						
							|  |  |  | 		if (pcie_cap_reg) { | 
					
						
							|  |  |  | 			u16 stat; | 
					
						
							|  |  |  | 			u32 caps; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			pci_read_config_word(a->pcid, | 
					
						
							|  |  |  | 					     pcie_cap_reg + PCI_EXP_LNKSTA, | 
					
						
							|  |  |  | 					     &stat); | 
					
						
							|  |  |  | 			pci_read_config_dword(a->pcid, | 
					
						
							|  |  |  | 					      pcie_cap_reg + PCI_EXP_LNKCAP, | 
					
						
							|  |  |  | 					      &caps); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			gai->pci.link_speed_curr = | 
					
						
							|  |  |  | 				(u8)(stat & PCI_EXP_LNKSTA_CLS); | 
					
						
							|  |  |  | 			gai->pci.link_speed_max = | 
					
						
							|  |  |  | 				(u8)(caps & PCI_EXP_LNKCAP_SLS); | 
					
						
							|  |  |  | 			gai->pci.link_width_curr = | 
					
						
							|  |  |  | 				(u8)((stat & PCI_EXP_LNKSTA_NLW) | 
					
						
							|  |  |  | 				     >> PCI_EXP_LNKSTA_NLW_SHIFT); | 
					
						
							|  |  |  | 			gai->pci.link_width_max = | 
					
						
							|  |  |  | 				(u8)((caps & PCI_EXP_LNKCAP_MLW) | 
					
						
							|  |  |  | 				     >> 4); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		gai->pci.msi_vector_cnt = 1; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (a->pcid->msix_enabled) | 
					
						
							|  |  |  | 			gai->pci.interrupt_mode = ATTO_GAI_PCIIM_MSIX; | 
					
						
							|  |  |  | 		else if (a->pcid->msi_enabled) | 
					
						
							|  |  |  | 			gai->pci.interrupt_mode = ATTO_GAI_PCIIM_MSI; | 
					
						
							|  |  |  | 		else | 
					
						
							|  |  |  | 			gai->pci.interrupt_mode = ATTO_GAI_PCIIM_LEGACY; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		gai->adap_type = ATTO_GAI_AT_ESASRAID2; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-10-01 14:26:01 -04:00
										 |  |  | 		if (test_bit(AF2_THUNDERLINK, &a->flags2)) | 
					
						
							| 
									
										
										
										
											2013-08-23 10:35:45 -04:00
										 |  |  | 			gai->adap_type = ATTO_GAI_AT_TLSASHBA; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-10-01 14:26:01 -04:00
										 |  |  | 		if (test_bit(AF_DEGRADED_MODE, &a->flags)) | 
					
						
							| 
									
										
										
										
											2013-08-23 10:35:45 -04:00
										 |  |  | 			gai->adap_flags |= ATTO_GAI_AF_DEGRADED; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		gai->adap_flags |= ATTO_GAI_AF_SPT_SUPP | | 
					
						
							|  |  |  | 				   ATTO_GAI_AF_DEVADDR_SUPP; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (a->pcid->subsystem_device == ATTO_ESAS_R60F | 
					
						
							|  |  |  | 		    || a->pcid->subsystem_device == ATTO_ESAS_R608 | 
					
						
							|  |  |  | 		    || a->pcid->subsystem_device == ATTO_ESAS_R644 | 
					
						
							|  |  |  | 		    || a->pcid->subsystem_device == ATTO_TSSC_3808E) | 
					
						
							|  |  |  | 			gai->adap_flags |= ATTO_GAI_AF_VIRT_SES; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		gai->num_ports = ESAS2R_NUM_PHYS; | 
					
						
							|  |  |  | 		gai->num_phys = ESAS2R_NUM_PHYS; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		strcpy(gai->firmware_rev, a->fw_rev); | 
					
						
							|  |  |  | 		strcpy(gai->flash_rev, a->flash_rev); | 
					
						
							|  |  |  | 		strcpy(gai->model_name_short, esas2r_get_model_name_short(a)); | 
					
						
							|  |  |  | 		strcpy(gai->model_name, esas2r_get_model_name(a)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		gai->num_targets = ESAS2R_MAX_TARGETS; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		gai->num_busses = 1; | 
					
						
							|  |  |  | 		gai->num_targsper_bus = gai->num_targets; | 
					
						
							|  |  |  | 		gai->num_lunsper_targ = 256; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (a->pcid->subsystem_device == ATTO_ESAS_R6F0 | 
					
						
							|  |  |  | 		    || a->pcid->subsystem_device == ATTO_ESAS_R60F) | 
					
						
							|  |  |  | 			gai->num_connectors = 4; | 
					
						
							|  |  |  | 		else | 
					
						
							|  |  |  | 			gai->num_connectors = 2; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		gai->adap_flags2 |= ATTO_GAI_AF2_ADAP_CTRL_SUPP; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		gai->num_targets_backend = a->num_targets_backend; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		gai->tunnel_flags = a->ioctl_tunnel | 
					
						
							|  |  |  | 				    & (ATTO_GAI_TF_MEM_RW | 
					
						
							|  |  |  | 				       | ATTO_GAI_TF_TRACE | 
					
						
							|  |  |  | 				       | ATTO_GAI_TF_SCSI_PASS_THRU | 
					
						
							|  |  |  | 				       | ATTO_GAI_TF_GET_DEV_ADDR | 
					
						
							|  |  |  | 				       | ATTO_GAI_TF_PHY_CTRL | 
					
						
							|  |  |  | 				       | ATTO_GAI_TF_CONN_CTRL | 
					
						
							|  |  |  | 				       | ATTO_GAI_TF_GET_DEV_INFO); | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case ATTO_FUNC_GET_ADAP_ADDR: | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		struct atto_hba_get_adapter_address *gaa = | 
					
						
							|  |  |  | 			&hi->data.get_adap_addr; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (hi->flags & HBAF_TUNNEL) { | 
					
						
							|  |  |  | 			hi->status = ATTO_STS_UNSUPPORTED; | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (hi->version > ATTO_VER_GET_ADAP_ADDR0) { | 
					
						
							|  |  |  | 			hi->status = ATTO_STS_INV_VERSION; | 
					
						
							|  |  |  | 			hi->version = ATTO_VER_GET_ADAP_ADDR0; | 
					
						
							|  |  |  | 		} else if (gaa->addr_type == ATTO_GAA_AT_PORT | 
					
						
							|  |  |  | 			   || gaa->addr_type == ATTO_GAA_AT_NODE) { | 
					
						
							|  |  |  | 			if (gaa->addr_type == ATTO_GAA_AT_PORT | 
					
						
							|  |  |  | 			    && gaa->port_id >= ESAS2R_NUM_PHYS) { | 
					
						
							|  |  |  | 				hi->status = ATTO_STS_NOT_APPL; | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				memcpy((u64 *)gaa->address, | 
					
						
							|  |  |  | 				       &a->nvram->sas_addr[0], sizeof(u64)); | 
					
						
							|  |  |  | 				gaa->addr_len = sizeof(u64); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			hi->status = ATTO_STS_INV_PARAM; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case ATTO_FUNC_MEM_RW: | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		if (hi->flags & HBAF_TUNNEL) { | 
					
						
							|  |  |  | 			if (hba_ioctl_tunnel(a, hi, rq, sgc)) | 
					
						
							|  |  |  | 				return true; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		hi->status = ATTO_STS_UNSUPPORTED; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case ATTO_FUNC_TRACE: | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		struct atto_hba_trace *trc = &hi->data.trace; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (hi->flags & HBAF_TUNNEL) { | 
					
						
							|  |  |  | 			if (hba_ioctl_tunnel(a, hi, rq, sgc)) | 
					
						
							|  |  |  | 				return true; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (hi->version > ATTO_VER_TRACE1) { | 
					
						
							|  |  |  | 			hi->status = ATTO_STS_INV_VERSION; | 
					
						
							|  |  |  | 			hi->version = ATTO_VER_TRACE1; | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (trc->trace_type == ATTO_TRC_TT_FWCOREDUMP | 
					
						
							|  |  |  | 		    && hi->version >= ATTO_VER_TRACE1) { | 
					
						
							|  |  |  | 			if (trc->trace_func == ATTO_TRC_TF_UPLOAD) { | 
					
						
							|  |  |  | 				u32 len = hi->data_length; | 
					
						
							|  |  |  | 				u32 offset = trc->current_offset; | 
					
						
							|  |  |  | 				u32 total_len = ESAS2R_FWCOREDUMP_SZ; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				/* Size is zero if a core dump isn't present */ | 
					
						
							| 
									
										
										
										
											2013-10-01 14:26:01 -04:00
										 |  |  | 				if (!test_bit(AF2_COREDUMP_SAVED, &a->flags2)) | 
					
						
							| 
									
										
										
										
											2013-08-23 10:35:45 -04:00
										 |  |  | 					total_len = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				if (len > total_len) | 
					
						
							|  |  |  | 					len = total_len; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				if (offset >= total_len | 
					
						
							|  |  |  | 				    || offset + len > total_len | 
					
						
							|  |  |  | 				    || len == 0) { | 
					
						
							|  |  |  | 					hi->status = ATTO_STS_INV_PARAM; | 
					
						
							|  |  |  | 					break; | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				memcpy(trc + 1, | 
					
						
							|  |  |  | 				       a->fw_coredump_buff + offset, | 
					
						
							|  |  |  | 				       len); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				hi->data_length = len; | 
					
						
							|  |  |  | 			} else if (trc->trace_func == ATTO_TRC_TF_RESET) { | 
					
						
							|  |  |  | 				memset(a->fw_coredump_buff, 0, | 
					
						
							|  |  |  | 				       ESAS2R_FWCOREDUMP_SZ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-10-01 14:26:01 -04:00
										 |  |  | 				clear_bit(AF2_COREDUMP_SAVED, &a->flags2); | 
					
						
							| 
									
										
										
										
											2013-08-23 10:35:45 -04:00
										 |  |  | 			} else if (trc->trace_func != ATTO_TRC_TF_GET_INFO) { | 
					
						
							|  |  |  | 				hi->status = ATTO_STS_UNSUPPORTED; | 
					
						
							|  |  |  | 				break; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			/* Always return all the info we can. */ | 
					
						
							|  |  |  | 			trc->trace_mask = 0; | 
					
						
							|  |  |  | 			trc->current_offset = 0; | 
					
						
							|  |  |  | 			trc->total_length = ESAS2R_FWCOREDUMP_SZ; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			/* Return zero length buffer if core dump not present */ | 
					
						
							| 
									
										
										
										
											2013-10-01 14:26:01 -04:00
										 |  |  | 			if (!test_bit(AF2_COREDUMP_SAVED, &a->flags2)) | 
					
						
							| 
									
										
										
										
											2013-08-23 10:35:45 -04:00
										 |  |  | 				trc->total_length = 0; | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			hi->status = ATTO_STS_UNSUPPORTED; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case ATTO_FUNC_SCSI_PASS_THRU: | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		struct atto_hba_scsi_pass_thru *spt = &hi->data.scsi_pass_thru; | 
					
						
							|  |  |  | 		struct scsi_lun lun; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		memcpy(&lun, spt->lun, sizeof(struct scsi_lun)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (hi->flags & HBAF_TUNNEL) { | 
					
						
							|  |  |  | 			if (hba_ioctl_tunnel(a, hi, rq, sgc)) | 
					
						
							|  |  |  | 				return true; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (hi->version > ATTO_VER_SCSI_PASS_THRU0) { | 
					
						
							|  |  |  | 			hi->status = ATTO_STS_INV_VERSION; | 
					
						
							|  |  |  | 			hi->version = ATTO_VER_SCSI_PASS_THRU0; | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (spt->target_id >= ESAS2R_MAX_TARGETS || !check_lun(lun)) { | 
					
						
							|  |  |  | 			hi->status = ATTO_STS_INV_PARAM; | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		esas2r_sgc_init(sgc, a, rq, NULL); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		sgc->length = hi->data_length; | 
					
						
							|  |  |  | 		sgc->cur_offset += offsetof(struct atto_ioctl, data.byte) | 
					
						
							|  |  |  | 				   + sizeof(struct atto_hba_scsi_pass_thru); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* Finish request initialization */ | 
					
						
							|  |  |  | 		rq->target_id = (u16)spt->target_id; | 
					
						
							|  |  |  | 		rq->vrq->scsi.flags |= cpu_to_le32(spt->lun[1]); | 
					
						
							|  |  |  | 		memcpy(rq->vrq->scsi.cdb, spt->cdb, 16); | 
					
						
							|  |  |  | 		rq->vrq->scsi.length = cpu_to_le32(hi->data_length); | 
					
						
							|  |  |  | 		rq->sense_len = spt->sense_length; | 
					
						
							|  |  |  | 		rq->sense_buf = (u8 *)spt->sense_data; | 
					
						
							|  |  |  | 		/* NOTE: we ignore spt->timeout */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/*
 | 
					
						
							|  |  |  | 		 * always usurp the completion callback since the interrupt | 
					
						
							|  |  |  | 		 * callback mechanism may be used. | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		rq->aux_req_cx = hi; | 
					
						
							|  |  |  | 		rq->aux_req_cb = rq->comp_cb; | 
					
						
							|  |  |  | 		rq->comp_cb = scsi_passthru_comp_cb; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (spt->flags & ATTO_SPTF_DATA_IN) { | 
					
						
							|  |  |  | 			rq->vrq->scsi.flags |= cpu_to_le32(FCP_CMND_RDD); | 
					
						
							|  |  |  | 		} else if (spt->flags & ATTO_SPTF_DATA_OUT) { | 
					
						
							|  |  |  | 			rq->vrq->scsi.flags |= cpu_to_le32(FCP_CMND_WRD); | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			if (sgc->length) { | 
					
						
							|  |  |  | 				hi->status = ATTO_STS_INV_PARAM; | 
					
						
							|  |  |  | 				break; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (spt->flags & ATTO_SPTF_ORDERED_Q) | 
					
						
							|  |  |  | 			rq->vrq->scsi.flags |= | 
					
						
							|  |  |  | 				cpu_to_le32(FCP_CMND_TA_ORDRD_Q); | 
					
						
							|  |  |  | 		else if (spt->flags & ATTO_SPTF_HEAD_OF_Q) | 
					
						
							|  |  |  | 			rq->vrq->scsi.flags |= cpu_to_le32(FCP_CMND_TA_HEAD_Q); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-10-01 14:26:01 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-08-23 10:35:45 -04:00
										 |  |  | 		if (!esas2r_build_sg_list(a, rq, sgc)) { | 
					
						
							|  |  |  | 			hi->status = ATTO_STS_OUT_OF_RSRC; | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		esas2r_start_request(a, rq); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return true; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case ATTO_FUNC_GET_DEV_ADDR: | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		struct atto_hba_get_device_address *gda = | 
					
						
							|  |  |  | 			&hi->data.get_dev_addr; | 
					
						
							|  |  |  | 		struct esas2r_target *t; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (hi->flags & HBAF_TUNNEL) { | 
					
						
							|  |  |  | 			if (hba_ioctl_tunnel(a, hi, rq, sgc)) | 
					
						
							|  |  |  | 				return true; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (hi->version > ATTO_VER_GET_DEV_ADDR0) { | 
					
						
							|  |  |  | 			hi->status = ATTO_STS_INV_VERSION; | 
					
						
							|  |  |  | 			hi->version = ATTO_VER_GET_DEV_ADDR0; | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (gda->target_id >= ESAS2R_MAX_TARGETS) { | 
					
						
							|  |  |  | 			hi->status = ATTO_STS_INV_PARAM; | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t = a->targetdb + (u16)gda->target_id; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (t->target_state != TS_PRESENT) { | 
					
						
							|  |  |  | 			hi->status = ATTO_STS_FAILED; | 
					
						
							|  |  |  | 		} else if (gda->addr_type == ATTO_GDA_AT_PORT) { | 
					
						
							|  |  |  | 			if (t->sas_addr == 0) { | 
					
						
							|  |  |  | 				hi->status = ATTO_STS_UNSUPPORTED; | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				*(u64 *)gda->address = t->sas_addr; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				gda->addr_len = sizeof(u64); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} else if (gda->addr_type == ATTO_GDA_AT_NODE) { | 
					
						
							|  |  |  | 			hi->status = ATTO_STS_NOT_APPL; | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			hi->status = ATTO_STS_INV_PARAM; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* update the target ID to the next one present. */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		gda->target_id = | 
					
						
							|  |  |  | 			esas2r_targ_db_find_next_present(a, | 
					
						
							|  |  |  | 							 (u16)gda->target_id); | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case ATTO_FUNC_PHY_CTRL: | 
					
						
							|  |  |  | 	case ATTO_FUNC_CONN_CTRL: | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		if (hba_ioctl_tunnel(a, hi, rq, sgc)) | 
					
						
							|  |  |  | 			return true; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case ATTO_FUNC_ADAP_CTRL: | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		struct atto_hba_adap_ctrl *ac = &hi->data.adap_ctrl; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (hi->flags & HBAF_TUNNEL) { | 
					
						
							|  |  |  | 			hi->status = ATTO_STS_UNSUPPORTED; | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (hi->version > ATTO_VER_ADAP_CTRL0) { | 
					
						
							|  |  |  | 			hi->status = ATTO_STS_INV_VERSION; | 
					
						
							|  |  |  | 			hi->version = ATTO_VER_ADAP_CTRL0; | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (ac->adap_func == ATTO_AC_AF_HARD_RST) { | 
					
						
							|  |  |  | 			esas2r_reset_adapter(a); | 
					
						
							|  |  |  | 		} else if (ac->adap_func != ATTO_AC_AF_GET_STATE) { | 
					
						
							|  |  |  | 			hi->status = ATTO_STS_UNSUPPORTED; | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-10-01 14:26:01 -04:00
										 |  |  | 		if (test_bit(AF_CHPRST_NEEDED, &a->flags)) | 
					
						
							| 
									
										
										
										
											2013-08-23 10:35:45 -04:00
										 |  |  | 			ac->adap_state = ATTO_AC_AS_RST_SCHED; | 
					
						
							| 
									
										
										
										
											2013-10-01 14:26:01 -04:00
										 |  |  | 		else if (test_bit(AF_CHPRST_PENDING, &a->flags)) | 
					
						
							| 
									
										
										
										
											2013-08-23 10:35:45 -04:00
										 |  |  | 			ac->adap_state = ATTO_AC_AS_RST_IN_PROG; | 
					
						
							| 
									
										
										
										
											2013-10-01 14:26:01 -04:00
										 |  |  | 		else if (test_bit(AF_DISC_PENDING, &a->flags)) | 
					
						
							| 
									
										
										
										
											2013-08-23 10:35:45 -04:00
										 |  |  | 			ac->adap_state = ATTO_AC_AS_RST_DISC; | 
					
						
							| 
									
										
										
										
											2013-10-01 14:26:01 -04:00
										 |  |  | 		else if (test_bit(AF_DISABLED, &a->flags)) | 
					
						
							| 
									
										
										
										
											2013-08-23 10:35:45 -04:00
										 |  |  | 			ac->adap_state = ATTO_AC_AS_DISABLED; | 
					
						
							| 
									
										
										
										
											2013-10-01 14:26:01 -04:00
										 |  |  | 		else if (test_bit(AF_DEGRADED_MODE, &a->flags)) | 
					
						
							| 
									
										
										
										
											2013-08-23 10:35:45 -04:00
										 |  |  | 			ac->adap_state = ATTO_AC_AS_DEGRADED; | 
					
						
							|  |  |  | 		else | 
					
						
							|  |  |  | 			ac->adap_state = ATTO_AC_AS_OK; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case ATTO_FUNC_GET_DEV_INFO: | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		struct atto_hba_get_device_info *gdi = &hi->data.get_dev_info; | 
					
						
							|  |  |  | 		struct esas2r_target *t; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (hi->flags & HBAF_TUNNEL) { | 
					
						
							|  |  |  | 			if (hba_ioctl_tunnel(a, hi, rq, sgc)) | 
					
						
							|  |  |  | 				return true; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (hi->version > ATTO_VER_GET_DEV_INFO0) { | 
					
						
							|  |  |  | 			hi->status = ATTO_STS_INV_VERSION; | 
					
						
							|  |  |  | 			hi->version = ATTO_VER_GET_DEV_INFO0; | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (gdi->target_id >= ESAS2R_MAX_TARGETS) { | 
					
						
							|  |  |  | 			hi->status = ATTO_STS_INV_PARAM; | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t = a->targetdb + (u16)gdi->target_id; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* update the target ID to the next one present. */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		gdi->target_id = | 
					
						
							|  |  |  | 			esas2r_targ_db_find_next_present(a, | 
					
						
							|  |  |  | 							 (u16)gdi->target_id); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (t->target_state != TS_PRESENT) { | 
					
						
							|  |  |  | 			hi->status = ATTO_STS_FAILED; | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		hi->status = ATTO_STS_UNSUPPORTED; | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		hi->status = ATTO_STS_INV_FUNC; | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return false; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void hba_ioctl_done_callback(struct esas2r_adapter *a, | 
					
						
							|  |  |  | 				    struct esas2r_request *rq, void *context) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct atto_ioctl *ioctl_hba = | 
					
						
							|  |  |  | 		(struct atto_ioctl *)esas2r_buffered_ioctl; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	esas2r_debug("hba_ioctl_done_callback %d", a->index); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (ioctl_hba->function == ATTO_FUNC_GET_ADAP_INFO) { | 
					
						
							|  |  |  | 		struct atto_hba_get_adapter_info *gai = | 
					
						
							|  |  |  | 			&ioctl_hba->data.get_adap_info; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		esas2r_debug("ATTO_FUNC_GET_ADAP_INFO"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		gai->drvr_rev_major = ESAS2R_MAJOR_REV; | 
					
						
							|  |  |  | 		gai->drvr_rev_minor = ESAS2R_MINOR_REV; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		strcpy(gai->drvr_rev_ascii, ESAS2R_VERSION_STR); | 
					
						
							|  |  |  | 		strcpy(gai->drvr_name, ESAS2R_DRVR_NAME); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		gai->num_busses = 1; | 
					
						
							|  |  |  | 		gai->num_targsper_bus = ESAS2R_MAX_ID + 1; | 
					
						
							|  |  |  | 		gai->num_lunsper_targ = 1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | u8 handle_hba_ioctl(struct esas2r_adapter *a, | 
					
						
							|  |  |  | 		    struct atto_ioctl *ioctl_hba) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct esas2r_buffered_ioctl bi; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	memset(&bi, 0, sizeof(bi)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	bi.a = a; | 
					
						
							|  |  |  | 	bi.ioctl = ioctl_hba; | 
					
						
							|  |  |  | 	bi.length = sizeof(struct atto_ioctl) + ioctl_hba->data_length; | 
					
						
							|  |  |  | 	bi.callback = hba_ioctl_callback; | 
					
						
							|  |  |  | 	bi.context = NULL; | 
					
						
							|  |  |  | 	bi.done_callback = hba_ioctl_done_callback; | 
					
						
							|  |  |  | 	bi.done_context = NULL; | 
					
						
							|  |  |  | 	bi.offset = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return handle_buffered_ioctl(&bi); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int esas2r_write_params(struct esas2r_adapter *a, struct esas2r_request *rq, | 
					
						
							|  |  |  | 			struct esas2r_sas_nvram *data) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int result = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	a->nvram_command_done = 0; | 
					
						
							|  |  |  | 	rq->comp_cb = complete_nvr_req; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (esas2r_nvram_write(a, rq, data)) { | 
					
						
							|  |  |  | 		/* now wait around for it to complete. */ | 
					
						
							|  |  |  | 		while (!a->nvram_command_done) | 
					
						
							|  |  |  | 			wait_event_interruptible(a->nvram_waiter, | 
					
						
							|  |  |  | 						 a->nvram_command_done); | 
					
						
							|  |  |  | 		; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* done, check the status. */ | 
					
						
							|  |  |  | 		if (rq->req_stat == RS_SUCCESS) | 
					
						
							|  |  |  | 			result = 1; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return result; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* This function only cares about ATTO-specific ioctls (atto_express_ioctl) */ | 
					
						
							|  |  |  | int esas2r_ioctl_handler(void *hostdata, int cmd, void __user *arg) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct atto_express_ioctl *ioctl = NULL; | 
					
						
							|  |  |  | 	struct esas2r_adapter *a; | 
					
						
							|  |  |  | 	struct esas2r_request *rq; | 
					
						
							|  |  |  | 	u16 code; | 
					
						
							|  |  |  | 	int err; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	esas2r_log(ESAS2R_LOG_DEBG, "ioctl (%p, %x, %p)", hostdata, cmd, arg); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if ((arg == NULL) | 
					
						
							|  |  |  | 	    || (cmd < EXPRESS_IOCTL_MIN) | 
					
						
							|  |  |  | 	    || (cmd > EXPRESS_IOCTL_MAX)) | 
					
						
							|  |  |  | 		return -ENOTSUPP; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!access_ok(VERIFY_WRITE, arg, sizeof(struct atto_express_ioctl))) { | 
					
						
							|  |  |  | 		esas2r_log(ESAS2R_LOG_WARN, | 
					
						
							|  |  |  | 			   "ioctl_handler access_ok failed for cmd %d, " | 
					
						
							|  |  |  | 			   "address %p", cmd, | 
					
						
							|  |  |  | 			   arg); | 
					
						
							|  |  |  | 		return -EFAULT; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* allocate a kernel memory buffer for the IOCTL data */ | 
					
						
							|  |  |  | 	ioctl = kzalloc(sizeof(struct atto_express_ioctl), GFP_KERNEL); | 
					
						
							|  |  |  | 	if (ioctl == NULL) { | 
					
						
							|  |  |  | 		esas2r_log(ESAS2R_LOG_WARN, | 
					
						
							|  |  |  | 			   "ioctl_handler kzalloc failed for %d bytes", | 
					
						
							|  |  |  | 			   sizeof(struct atto_express_ioctl)); | 
					
						
							|  |  |  | 		return -ENOMEM; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	err = __copy_from_user(ioctl, arg, sizeof(struct atto_express_ioctl)); | 
					
						
							|  |  |  | 	if (err != 0) { | 
					
						
							|  |  |  | 		esas2r_log(ESAS2R_LOG_WARN, | 
					
						
							|  |  |  | 			   "copy_from_user didn't copy everything (err %d, cmd %d)", | 
					
						
							|  |  |  | 			   err, | 
					
						
							|  |  |  | 			   cmd); | 
					
						
							|  |  |  | 		kfree(ioctl); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return -EFAULT; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* verify the signature */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (memcmp(ioctl->header.signature, | 
					
						
							|  |  |  | 		   EXPRESS_IOCTL_SIGNATURE, | 
					
						
							|  |  |  | 		   EXPRESS_IOCTL_SIGNATURE_SIZE) != 0) { | 
					
						
							|  |  |  | 		esas2r_log(ESAS2R_LOG_WARN, "invalid signature"); | 
					
						
							|  |  |  | 		kfree(ioctl); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return -ENOTSUPP; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* assume success */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ioctl->header.return_code = IOCTL_SUCCESS; | 
					
						
							|  |  |  | 	err = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/*
 | 
					
						
							|  |  |  | 	 * handle EXPRESS_IOCTL_GET_CHANNELS | 
					
						
							|  |  |  | 	 * without paying attention to channel | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (cmd == EXPRESS_IOCTL_GET_CHANNELS) { | 
					
						
							|  |  |  | 		int i = 0, k = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		ioctl->data.chanlist.num_channels = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		while (i < MAX_ADAPTERS) { | 
					
						
							|  |  |  | 			if (esas2r_adapters[i]) { | 
					
						
							|  |  |  | 				ioctl->data.chanlist.num_channels++; | 
					
						
							|  |  |  | 				ioctl->data.chanlist.channel[k] = i; | 
					
						
							|  |  |  | 				k++; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			i++; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		goto ioctl_done; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* get the channel */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (ioctl->header.channel == 0xFF) { | 
					
						
							|  |  |  | 		a = (struct esas2r_adapter *)hostdata; | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		a = esas2r_adapters[ioctl->header.channel]; | 
					
						
							|  |  |  | 		if (ioctl->header.channel >= MAX_ADAPTERS || (a == NULL)) { | 
					
						
							|  |  |  | 			ioctl->header.return_code = IOCTL_BAD_CHANNEL; | 
					
						
							|  |  |  | 			esas2r_log(ESAS2R_LOG_WARN, "bad channel value"); | 
					
						
							|  |  |  | 			kfree(ioctl); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return -ENOTSUPP; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch (cmd) { | 
					
						
							|  |  |  | 	case EXPRESS_IOCTL_RW_FIRMWARE: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (ioctl->data.fwrw.img_type == FW_IMG_FM_API) { | 
					
						
							|  |  |  | 			err = esas2r_write_fw(a, | 
					
						
							|  |  |  | 					      (char *)ioctl->data.fwrw.image, | 
					
						
							|  |  |  | 					      0, | 
					
						
							|  |  |  | 					      sizeof(struct | 
					
						
							|  |  |  | 						     atto_express_ioctl)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if (err >= 0) { | 
					
						
							|  |  |  | 				err = esas2r_read_fw(a, | 
					
						
							|  |  |  | 						     (char *)ioctl->data.fwrw. | 
					
						
							|  |  |  | 						     image, | 
					
						
							|  |  |  | 						     0, | 
					
						
							|  |  |  | 						     sizeof(struct | 
					
						
							|  |  |  | 							    atto_express_ioctl)); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} else if (ioctl->data.fwrw.img_type == FW_IMG_FS_API) { | 
					
						
							|  |  |  | 			err = esas2r_write_fs(a, | 
					
						
							|  |  |  | 					      (char *)ioctl->data.fwrw.image, | 
					
						
							|  |  |  | 					      0, | 
					
						
							|  |  |  | 					      sizeof(struct | 
					
						
							|  |  |  | 						     atto_express_ioctl)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if (err >= 0) { | 
					
						
							|  |  |  | 				err = esas2r_read_fs(a, | 
					
						
							|  |  |  | 						     (char *)ioctl->data.fwrw. | 
					
						
							|  |  |  | 						     image, | 
					
						
							|  |  |  | 						     0, | 
					
						
							|  |  |  | 						     sizeof(struct | 
					
						
							|  |  |  | 							    atto_express_ioctl)); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			ioctl->header.return_code = IOCTL_BAD_FLASH_IMGTYPE; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case EXPRESS_IOCTL_READ_PARAMS: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		memcpy(ioctl->data.prw.data_buffer, a->nvram, | 
					
						
							|  |  |  | 		       sizeof(struct esas2r_sas_nvram)); | 
					
						
							|  |  |  | 		ioctl->data.prw.code = 1; | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case EXPRESS_IOCTL_WRITE_PARAMS: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		rq = esas2r_alloc_request(a); | 
					
						
							|  |  |  | 		if (rq == NULL) { | 
					
						
							|  |  |  | 			up(&a->nvram_semaphore); | 
					
						
							|  |  |  | 			ioctl->data.prw.code = 0; | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		code = esas2r_write_params(a, rq, | 
					
						
							|  |  |  | 					   (struct esas2r_sas_nvram *)ioctl->data.prw.data_buffer); | 
					
						
							|  |  |  | 		ioctl->data.prw.code = code; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		esas2r_free_request(a, rq); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case EXPRESS_IOCTL_DEFAULT_PARAMS: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		esas2r_nvram_get_defaults(a, | 
					
						
							|  |  |  | 					  (struct esas2r_sas_nvram *)ioctl->data.prw.data_buffer); | 
					
						
							|  |  |  | 		ioctl->data.prw.code = 1; | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case EXPRESS_IOCTL_CHAN_INFO: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		ioctl->data.chaninfo.major_rev = ESAS2R_MAJOR_REV; | 
					
						
							|  |  |  | 		ioctl->data.chaninfo.minor_rev = ESAS2R_MINOR_REV; | 
					
						
							|  |  |  | 		ioctl->data.chaninfo.IRQ = a->pcid->irq; | 
					
						
							|  |  |  | 		ioctl->data.chaninfo.device_id = a->pcid->device; | 
					
						
							|  |  |  | 		ioctl->data.chaninfo.vendor_id = a->pcid->vendor; | 
					
						
							|  |  |  | 		ioctl->data.chaninfo.ven_dev_id = a->pcid->subsystem_device; | 
					
						
							|  |  |  | 		ioctl->data.chaninfo.revision_id = a->pcid->revision; | 
					
						
							|  |  |  | 		ioctl->data.chaninfo.pci_bus = a->pcid->bus->number; | 
					
						
							|  |  |  | 		ioctl->data.chaninfo.pci_dev_func = a->pcid->devfn; | 
					
						
							|  |  |  | 		ioctl->data.chaninfo.core_rev = 0; | 
					
						
							|  |  |  | 		ioctl->data.chaninfo.host_no = a->host->host_no; | 
					
						
							|  |  |  | 		ioctl->data.chaninfo.hbaapi_rev = 0; | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case EXPRESS_IOCTL_SMP: | 
					
						
							|  |  |  | 		ioctl->header.return_code = handle_smp_ioctl(a, | 
					
						
							|  |  |  | 							     &ioctl->data. | 
					
						
							|  |  |  | 							     ioctl_smp); | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case EXPRESS_CSMI: | 
					
						
							|  |  |  | 		ioctl->header.return_code = | 
					
						
							|  |  |  | 			handle_csmi_ioctl(a, &ioctl->data.csmi); | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case EXPRESS_IOCTL_HBA: | 
					
						
							|  |  |  | 		ioctl->header.return_code = handle_hba_ioctl(a, | 
					
						
							|  |  |  | 							     &ioctl->data. | 
					
						
							|  |  |  | 							     ioctl_hba); | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case EXPRESS_IOCTL_VDA: | 
					
						
							|  |  |  | 		err = esas2r_write_vda(a, | 
					
						
							|  |  |  | 				       (char *)&ioctl->data.ioctl_vda, | 
					
						
							|  |  |  | 				       0, | 
					
						
							|  |  |  | 				       sizeof(struct atto_ioctl_vda) + | 
					
						
							|  |  |  | 				       ioctl->data.ioctl_vda.data_length); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (err >= 0) { | 
					
						
							|  |  |  | 			err = esas2r_read_vda(a, | 
					
						
							|  |  |  | 					      (char *)&ioctl->data.ioctl_vda, | 
					
						
							|  |  |  | 					      0, | 
					
						
							|  |  |  | 					      sizeof(struct atto_ioctl_vda) + | 
					
						
							|  |  |  | 					      ioctl->data.ioctl_vda.data_length); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case EXPRESS_IOCTL_GET_MOD_INFO: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		ioctl->data.modinfo.adapter = a; | 
					
						
							|  |  |  | 		ioctl->data.modinfo.pci_dev = a->pcid; | 
					
						
							|  |  |  | 		ioctl->data.modinfo.scsi_host = a->host; | 
					
						
							|  |  |  | 		ioctl->data.modinfo.host_no = a->host->host_no; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		esas2r_debug("esas2r_ioctl invalid cmd %p!", cmd); | 
					
						
							|  |  |  | 		ioctl->header.return_code = IOCTL_ERR_INVCMD; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ioctl_done: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (err < 0) { | 
					
						
							|  |  |  | 		esas2r_log(ESAS2R_LOG_WARN, "err %d on ioctl cmd %d", err, | 
					
						
							|  |  |  | 			   cmd); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		switch (err) { | 
					
						
							|  |  |  | 		case -ENOMEM: | 
					
						
							|  |  |  | 		case -EBUSY: | 
					
						
							|  |  |  | 			ioctl->header.return_code = IOCTL_OUT_OF_RESOURCES; | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		case -ENOSYS: | 
					
						
							|  |  |  | 		case -EINVAL: | 
					
						
							|  |  |  | 			ioctl->header.return_code = IOCTL_INVALID_PARAM; | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		ioctl->header.return_code = IOCTL_GENERAL_ERROR; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Always copy the buffer back, if only to pick up the status */ | 
					
						
							|  |  |  | 	err = __copy_to_user(arg, ioctl, sizeof(struct atto_express_ioctl)); | 
					
						
							|  |  |  | 	if (err != 0) { | 
					
						
							|  |  |  | 		esas2r_log(ESAS2R_LOG_WARN, | 
					
						
							|  |  |  | 			   "ioctl_handler copy_to_user didn't copy " | 
					
						
							|  |  |  | 			   "everything (err %d, cmd %d)", err, | 
					
						
							|  |  |  | 			   cmd); | 
					
						
							|  |  |  | 		kfree(ioctl); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return -EFAULT; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	kfree(ioctl); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int esas2r_ioctl(struct scsi_device *sd, int cmd, void __user *arg) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	return esas2r_ioctl_handler(sd->host->hostdata, cmd, arg); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void free_fw_buffers(struct esas2r_adapter *a) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	if (a->firmware.data) { | 
					
						
							|  |  |  | 		dma_free_coherent(&a->pcid->dev, | 
					
						
							|  |  |  | 				  (size_t)a->firmware.orig_len, | 
					
						
							|  |  |  | 				  a->firmware.data, | 
					
						
							|  |  |  | 				  (dma_addr_t)a->firmware.phys); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		a->firmware.data = NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int allocate_fw_buffers(struct esas2r_adapter *a, u32 length) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	free_fw_buffers(a); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	a->firmware.orig_len = length; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	a->firmware.data = (u8 *)dma_alloc_coherent(&a->pcid->dev, | 
					
						
							|  |  |  | 						    (size_t)length, | 
					
						
							|  |  |  | 						    (dma_addr_t *)&a->firmware. | 
					
						
							|  |  |  | 						    phys, | 
					
						
							|  |  |  | 						    GFP_KERNEL); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!a->firmware.data) { | 
					
						
							|  |  |  | 		esas2r_debug("buffer alloc failed!"); | 
					
						
							|  |  |  | 		return 0; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 1; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Handle a call to read firmware. */ | 
					
						
							|  |  |  | int esas2r_read_fw(struct esas2r_adapter *a, char *buf, long off, int count) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	esas2r_trace_enter(); | 
					
						
							|  |  |  | 	/* if the cached header is a status, simply copy it over and return. */ | 
					
						
							|  |  |  | 	if (a->firmware.state == FW_STATUS_ST) { | 
					
						
							|  |  |  | 		int size = min_t(int, count, sizeof(a->firmware.header)); | 
					
						
							|  |  |  | 		esas2r_trace_exit(); | 
					
						
							|  |  |  | 		memcpy(buf, &a->firmware.header, size); | 
					
						
							|  |  |  | 		esas2r_debug("esas2r_read_fw: STATUS size %d", size); | 
					
						
							|  |  |  | 		return size; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/*
 | 
					
						
							|  |  |  | 	 * if the cached header is a command, do it if at | 
					
						
							|  |  |  | 	 * offset 0, otherwise copy the pieces. | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (a->firmware.state == FW_COMMAND_ST) { | 
					
						
							|  |  |  | 		u32 length = a->firmware.header.length; | 
					
						
							|  |  |  | 		esas2r_trace_exit(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		esas2r_debug("esas2r_read_fw: COMMAND length %d off %d", | 
					
						
							|  |  |  | 			     length, | 
					
						
							|  |  |  | 			     off); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (off == 0) { | 
					
						
							|  |  |  | 			if (a->firmware.header.action == FI_ACT_UP) { | 
					
						
							|  |  |  | 				if (!allocate_fw_buffers(a, length)) | 
					
						
							|  |  |  | 					return -ENOMEM; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				/* copy header over */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				memcpy(a->firmware.data, | 
					
						
							|  |  |  | 				       &a->firmware.header, | 
					
						
							|  |  |  | 				       sizeof(a->firmware.header)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				do_fm_api(a, | 
					
						
							|  |  |  | 					  (struct esas2r_flash_img *)a->firmware.data); | 
					
						
							|  |  |  | 			} else if (a->firmware.header.action == FI_ACT_UPSZ) { | 
					
						
							|  |  |  | 				int size = | 
					
						
							|  |  |  | 					min((int)count, | 
					
						
							|  |  |  | 					    (int)sizeof(a->firmware.header)); | 
					
						
							|  |  |  | 				do_fm_api(a, &a->firmware.header); | 
					
						
							|  |  |  | 				memcpy(buf, &a->firmware.header, size); | 
					
						
							|  |  |  | 				esas2r_debug("FI_ACT_UPSZ size %d", size); | 
					
						
							|  |  |  | 				return size; | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				esas2r_debug("invalid action %d", | 
					
						
							|  |  |  | 					     a->firmware.header.action); | 
					
						
							|  |  |  | 				return -ENOSYS; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (count + off > length) | 
					
						
							|  |  |  | 			count = length - off; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (count < 0) | 
					
						
							|  |  |  | 			return 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (!a->firmware.data) { | 
					
						
							|  |  |  | 			esas2r_debug( | 
					
						
							|  |  |  | 				"read: nonzero offset but no buffer available!"); | 
					
						
							|  |  |  | 			return -ENOMEM; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		esas2r_debug("esas2r_read_fw: off %d count %d length %d ", off, | 
					
						
							|  |  |  | 			     count, | 
					
						
							|  |  |  | 			     length); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		memcpy(buf, &a->firmware.data[off], count); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* when done, release the buffer */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (length <= off + count) { | 
					
						
							|  |  |  | 			esas2r_debug("esas2r_read_fw: freeing buffer!"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			free_fw_buffers(a); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return count; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	esas2r_trace_exit(); | 
					
						
							|  |  |  | 	esas2r_debug("esas2r_read_fw: invalid firmware state %d", | 
					
						
							|  |  |  | 		     a->firmware.state); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return -EINVAL; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Handle a call to write firmware. */ | 
					
						
							|  |  |  | int esas2r_write_fw(struct esas2r_adapter *a, const char *buf, long off, | 
					
						
							|  |  |  | 		    int count) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	u32 length; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (off == 0) { | 
					
						
							|  |  |  | 		struct esas2r_flash_img *header = | 
					
						
							|  |  |  | 			(struct esas2r_flash_img *)buf; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* assume version 0 flash image */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		int min_size = sizeof(struct esas2r_flash_img_v0); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		a->firmware.state = FW_INVALID_ST; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* validate the version field first */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (count < 4 | 
					
						
							|  |  |  | 		    ||  header->fi_version > FI_VERSION_1) { | 
					
						
							|  |  |  | 			esas2r_debug( | 
					
						
							|  |  |  | 				"esas2r_write_fw: short header or invalid version"); | 
					
						
							|  |  |  | 			return -EINVAL; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* See if its a version 1 flash image */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (header->fi_version == FI_VERSION_1) | 
					
						
							|  |  |  | 			min_size = sizeof(struct esas2r_flash_img); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* If this is the start, the header must be full and valid. */ | 
					
						
							|  |  |  | 		if (count < min_size) { | 
					
						
							|  |  |  | 			esas2r_debug("esas2r_write_fw: short header, aborting"); | 
					
						
							|  |  |  | 			return -EINVAL; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* Make sure the size is reasonable. */ | 
					
						
							|  |  |  | 		length = header->length; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (length > 1024 * 1024) { | 
					
						
							|  |  |  | 			esas2r_debug( | 
					
						
							|  |  |  | 				"esas2r_write_fw: hosed, length %d  fi_version %d", | 
					
						
							|  |  |  | 				length, header->fi_version); | 
					
						
							|  |  |  | 			return -EINVAL; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/*
 | 
					
						
							|  |  |  | 		 * If this is a write command, allocate memory because | 
					
						
							|  |  |  | 		 * we have to cache everything. otherwise, just cache | 
					
						
							|  |  |  | 		 * the header, because the read op will do the command. | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (header->action == FI_ACT_DOWN) { | 
					
						
							|  |  |  | 			if (!allocate_fw_buffers(a, length)) | 
					
						
							|  |  |  | 				return -ENOMEM; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			/*
 | 
					
						
							|  |  |  | 			 * Store the command, so there is context on subsequent | 
					
						
							|  |  |  | 			 * calls. | 
					
						
							|  |  |  | 			 */ | 
					
						
							|  |  |  | 			memcpy(&a->firmware.header, | 
					
						
							|  |  |  | 			       buf, | 
					
						
							|  |  |  | 			       sizeof(*header)); | 
					
						
							|  |  |  | 		} else if (header->action == FI_ACT_UP | 
					
						
							|  |  |  | 			   ||  header->action == FI_ACT_UPSZ) { | 
					
						
							|  |  |  | 			/* Save the command, result will be picked up on read */ | 
					
						
							|  |  |  | 			memcpy(&a->firmware.header, | 
					
						
							|  |  |  | 			       buf, | 
					
						
							|  |  |  | 			       sizeof(*header)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			a->firmware.state = FW_COMMAND_ST; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			esas2r_debug( | 
					
						
							|  |  |  | 				"esas2r_write_fw: COMMAND, count %d, action %d ", | 
					
						
							|  |  |  | 				count, header->action); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			/*
 | 
					
						
							|  |  |  | 			 * Pretend we took the whole buffer, | 
					
						
							|  |  |  | 			 * so we don't get bothered again. | 
					
						
							|  |  |  | 			 */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return count; | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			esas2r_debug("esas2r_write_fw: invalid action %d ", | 
					
						
							|  |  |  | 				     a->firmware.header.action); | 
					
						
							|  |  |  | 			return -ENOSYS; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		length = a->firmware.header.length; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/*
 | 
					
						
							|  |  |  | 	 * We only get here on a download command, regardless of offset. | 
					
						
							|  |  |  | 	 * the chunks written by the system need to be cached, and when | 
					
						
							|  |  |  | 	 * the final one arrives, issue the fmapi command. | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (off + count > length) | 
					
						
							|  |  |  | 		count = length - off; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (count > 0) { | 
					
						
							|  |  |  | 		esas2r_debug("esas2r_write_fw: off %d count %d length %d", off, | 
					
						
							|  |  |  | 			     count, | 
					
						
							|  |  |  | 			     length); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/*
 | 
					
						
							|  |  |  | 		 * On a full upload, the system tries sending the whole buffer. | 
					
						
							|  |  |  | 		 * there's nothing to do with it, so just drop it here, before | 
					
						
							|  |  |  | 		 * trying to copy over into unallocated memory! | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 		if (a->firmware.header.action == FI_ACT_UP) | 
					
						
							|  |  |  | 			return count; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (!a->firmware.data) { | 
					
						
							|  |  |  | 			esas2r_debug( | 
					
						
							|  |  |  | 				"write: nonzero offset but no buffer available!"); | 
					
						
							|  |  |  | 			return -ENOMEM; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		memcpy(&a->firmware.data[off], buf, count); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (length == off + count) { | 
					
						
							|  |  |  | 			do_fm_api(a, | 
					
						
							|  |  |  | 				  (struct esas2r_flash_img *)a->firmware.data); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			/*
 | 
					
						
							|  |  |  | 			 * Now copy the header result to be picked up by the | 
					
						
							|  |  |  | 			 * next read | 
					
						
							|  |  |  | 			 */ | 
					
						
							|  |  |  | 			memcpy(&a->firmware.header, | 
					
						
							|  |  |  | 			       a->firmware.data, | 
					
						
							|  |  |  | 			       sizeof(a->firmware.header)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			a->firmware.state = FW_STATUS_ST; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			esas2r_debug("write completed"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			/*
 | 
					
						
							|  |  |  | 			 * Since the system has the data buffered, the only way | 
					
						
							|  |  |  | 			 * this can leak is if a root user writes a program | 
					
						
							|  |  |  | 			 * that writes a shorter buffer than it claims, and the | 
					
						
							|  |  |  | 			 * copyin fails. | 
					
						
							|  |  |  | 			 */ | 
					
						
							|  |  |  | 			free_fw_buffers(a); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return count; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Callback for the completion of a VDA request. */ | 
					
						
							|  |  |  | static void vda_complete_req(struct esas2r_adapter *a, | 
					
						
							|  |  |  | 			     struct esas2r_request *rq) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	a->vda_command_done = 1; | 
					
						
							|  |  |  | 	wake_up_interruptible(&a->vda_waiter); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Scatter/gather callback for VDA requests */ | 
					
						
							|  |  |  | static u32 get_physaddr_vda(struct esas2r_sg_context *sgc, u64 *addr) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct esas2r_adapter *a = (struct esas2r_adapter *)sgc->adapter; | 
					
						
							|  |  |  | 	int offset = (u8 *)sgc->cur_offset - (u8 *)a->vda_buffer; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	(*addr) = a->ppvda_buffer + offset; | 
					
						
							|  |  |  | 	return VDA_MAX_BUFFER_SIZE - offset; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Handle a call to read a VDA command. */ | 
					
						
							|  |  |  | int esas2r_read_vda(struct esas2r_adapter *a, char *buf, long off, int count) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	if (!a->vda_buffer) | 
					
						
							|  |  |  | 		return -ENOMEM; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (off == 0) { | 
					
						
							|  |  |  | 		struct esas2r_request *rq; | 
					
						
							|  |  |  | 		struct atto_ioctl_vda *vi = | 
					
						
							|  |  |  | 			(struct atto_ioctl_vda *)a->vda_buffer; | 
					
						
							|  |  |  | 		struct esas2r_sg_context sgc; | 
					
						
							|  |  |  | 		bool wait_for_completion; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/*
 | 
					
						
							|  |  |  | 		 * Presumeably, someone has already written to the vda_buffer, | 
					
						
							|  |  |  | 		 * and now they are reading the node the response, so now we | 
					
						
							|  |  |  | 		 * will actually issue the request to the chip and reply. | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* allocate a request */ | 
					
						
							|  |  |  | 		rq = esas2r_alloc_request(a); | 
					
						
							|  |  |  | 		if (rq == NULL) { | 
					
						
							|  |  |  | 			esas2r_debug("esas2r_read_vda: out of requestss"); | 
					
						
							|  |  |  | 			return -EBUSY; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		rq->comp_cb = vda_complete_req; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		sgc.first_req = rq; | 
					
						
							|  |  |  | 		sgc.adapter = a; | 
					
						
							|  |  |  | 		sgc.cur_offset = a->vda_buffer + VDA_BUFFER_HEADER_SZ; | 
					
						
							|  |  |  | 		sgc.get_phys_addr = (PGETPHYSADDR)get_physaddr_vda; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		a->vda_command_done = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		wait_for_completion = | 
					
						
							|  |  |  | 			esas2r_process_vda_ioctl(a, vi, rq, &sgc); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (wait_for_completion) { | 
					
						
							|  |  |  | 			/* now wait around for it to complete. */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			while (!a->vda_command_done) | 
					
						
							|  |  |  | 				wait_event_interruptible(a->vda_waiter, | 
					
						
							|  |  |  | 							 a->vda_command_done); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		esas2r_free_request(a, (struct esas2r_request *)rq); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (off > VDA_MAX_BUFFER_SIZE) | 
					
						
							|  |  |  | 		return 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (count + off > VDA_MAX_BUFFER_SIZE) | 
					
						
							|  |  |  | 		count = VDA_MAX_BUFFER_SIZE - off; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (count < 0) | 
					
						
							|  |  |  | 		return 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	memcpy(buf, a->vda_buffer + off, count); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return count; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Handle a call to write a VDA command. */ | 
					
						
							|  |  |  | int esas2r_write_vda(struct esas2r_adapter *a, const char *buf, long off, | 
					
						
							|  |  |  | 		     int count) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	/*
 | 
					
						
							|  |  |  | 	 * allocate memory for it, if not already done.  once allocated, | 
					
						
							|  |  |  | 	 * we will keep it around until the driver is unloaded. | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!a->vda_buffer) { | 
					
						
							|  |  |  | 		dma_addr_t dma_addr; | 
					
						
							|  |  |  | 		a->vda_buffer = (u8 *)dma_alloc_coherent(&a->pcid->dev, | 
					
						
							|  |  |  | 							 (size_t) | 
					
						
							|  |  |  | 							 VDA_MAX_BUFFER_SIZE, | 
					
						
							|  |  |  | 							 &dma_addr, | 
					
						
							|  |  |  | 							 GFP_KERNEL); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		a->ppvda_buffer = dma_addr; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!a->vda_buffer) | 
					
						
							|  |  |  | 		return -ENOMEM; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (off > VDA_MAX_BUFFER_SIZE) | 
					
						
							|  |  |  | 		return 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (count + off > VDA_MAX_BUFFER_SIZE) | 
					
						
							|  |  |  | 		count = VDA_MAX_BUFFER_SIZE - off; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (count < 1) | 
					
						
							|  |  |  | 		return 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	memcpy(a->vda_buffer + off, buf, count); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return count; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Callback for the completion of an FS_API request.*/ | 
					
						
							|  |  |  | static void fs_api_complete_req(struct esas2r_adapter *a, | 
					
						
							|  |  |  | 				struct esas2r_request *rq) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	a->fs_api_command_done = 1; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	wake_up_interruptible(&a->fs_api_waiter); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Scatter/gather callback for VDA requests */ | 
					
						
							|  |  |  | static u32 get_physaddr_fs_api(struct esas2r_sg_context *sgc, u64 *addr) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct esas2r_adapter *a = (struct esas2r_adapter *)sgc->adapter; | 
					
						
							|  |  |  | 	struct esas2r_ioctl_fs *fs = | 
					
						
							|  |  |  | 		(struct esas2r_ioctl_fs *)a->fs_api_buffer; | 
					
						
							|  |  |  | 	u32 offset = (u8 *)sgc->cur_offset - (u8 *)fs; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	(*addr) = a->ppfs_api_buffer + offset; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return a->fs_api_buffer_size - offset; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Handle a call to read firmware via FS_API. */ | 
					
						
							|  |  |  | int esas2r_read_fs(struct esas2r_adapter *a, char *buf, long off, int count) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	if (!a->fs_api_buffer) | 
					
						
							|  |  |  | 		return -ENOMEM; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (off == 0) { | 
					
						
							|  |  |  | 		struct esas2r_request *rq; | 
					
						
							|  |  |  | 		struct esas2r_sg_context sgc; | 
					
						
							|  |  |  | 		struct esas2r_ioctl_fs *fs = | 
					
						
							|  |  |  | 			(struct esas2r_ioctl_fs *)a->fs_api_buffer; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* If another flash request is already in progress, return. */ | 
					
						
							|  |  |  | 		if (down_interruptible(&a->fs_api_semaphore)) { | 
					
						
							|  |  |  | busy: | 
					
						
							|  |  |  | 			fs->status = ATTO_STS_OUT_OF_RSRC; | 
					
						
							|  |  |  | 			return -EBUSY; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/*
 | 
					
						
							|  |  |  | 		 * Presumeably, someone has already written to the | 
					
						
							|  |  |  | 		 * fs_api_buffer, and now they are reading the node the | 
					
						
							|  |  |  | 		 * response, so now we will actually issue the request to the | 
					
						
							|  |  |  | 		 * chip and reply. Allocate a request | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		rq = esas2r_alloc_request(a); | 
					
						
							|  |  |  | 		if (rq == NULL) { | 
					
						
							|  |  |  | 			esas2r_debug("esas2r_read_fs: out of requests"); | 
					
						
							|  |  |  | 			up(&a->fs_api_semaphore); | 
					
						
							|  |  |  | 			goto busy; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		rq->comp_cb = fs_api_complete_req; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* Set up the SGCONTEXT for to build the s/g table */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		sgc.cur_offset = fs->data; | 
					
						
							|  |  |  | 		sgc.get_phys_addr = (PGETPHYSADDR)get_physaddr_fs_api; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		a->fs_api_command_done = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (!esas2r_process_fs_ioctl(a, fs, rq, &sgc)) { | 
					
						
							|  |  |  | 			if (fs->status == ATTO_STS_OUT_OF_RSRC) | 
					
						
							|  |  |  | 				count = -EBUSY; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			goto dont_wait; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* Now wait around for it to complete. */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		while (!a->fs_api_command_done) | 
					
						
							|  |  |  | 			wait_event_interruptible(a->fs_api_waiter, | 
					
						
							|  |  |  | 						 a->fs_api_command_done); | 
					
						
							|  |  |  | 		; | 
					
						
							|  |  |  | dont_wait: | 
					
						
							|  |  |  | 		/* Free the request and keep going */ | 
					
						
							|  |  |  | 		up(&a->fs_api_semaphore); | 
					
						
							|  |  |  | 		esas2r_free_request(a, (struct esas2r_request *)rq); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* Pick up possible error code from above */ | 
					
						
							|  |  |  | 		if (count < 0) | 
					
						
							|  |  |  | 			return count; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (off > a->fs_api_buffer_size) | 
					
						
							|  |  |  | 		return 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (count + off > a->fs_api_buffer_size) | 
					
						
							|  |  |  | 		count = a->fs_api_buffer_size - off; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (count < 0) | 
					
						
							|  |  |  | 		return 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	memcpy(buf, a->fs_api_buffer + off, count); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return count; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Handle a call to write firmware via FS_API. */ | 
					
						
							|  |  |  | int esas2r_write_fs(struct esas2r_adapter *a, const char *buf, long off, | 
					
						
							|  |  |  | 		    int count) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	if (off == 0) { | 
					
						
							|  |  |  | 		struct esas2r_ioctl_fs *fs = (struct esas2r_ioctl_fs *)buf; | 
					
						
							|  |  |  | 		u32 length = fs->command.length + offsetof( | 
					
						
							|  |  |  | 			struct esas2r_ioctl_fs, | 
					
						
							|  |  |  | 			data); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/*
 | 
					
						
							|  |  |  | 		 * Special case, for BEGIN commands, the length field | 
					
						
							|  |  |  | 		 * is lying to us, so just get enough for the header. | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (fs->command.command == ESAS2R_FS_CMD_BEGINW) | 
					
						
							|  |  |  | 			length = offsetof(struct esas2r_ioctl_fs, data); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/*
 | 
					
						
							|  |  |  | 		 * Beginning a command.  We assume we'll get at least | 
					
						
							|  |  |  | 		 * enough in the first write so we can look at the | 
					
						
							|  |  |  | 		 * header and see how much we need to alloc. | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (count < offsetof(struct esas2r_ioctl_fs, data)) | 
					
						
							|  |  |  | 			return -EINVAL; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* Allocate a buffer or use the existing buffer. */ | 
					
						
							|  |  |  | 		if (a->fs_api_buffer) { | 
					
						
							|  |  |  | 			if (a->fs_api_buffer_size < length) { | 
					
						
							|  |  |  | 				/* Free too-small buffer and get a new one */ | 
					
						
							|  |  |  | 				dma_free_coherent(&a->pcid->dev, | 
					
						
							|  |  |  | 						  (size_t)a->fs_api_buffer_size, | 
					
						
							|  |  |  | 						  a->fs_api_buffer, | 
					
						
							|  |  |  | 						  (dma_addr_t)a->ppfs_api_buffer); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				goto re_allocate_buffer; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | re_allocate_buffer: | 
					
						
							|  |  |  | 			a->fs_api_buffer_size = length; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			a->fs_api_buffer = (u8 *)dma_alloc_coherent( | 
					
						
							|  |  |  | 				&a->pcid->dev, | 
					
						
							|  |  |  | 				(size_t)a->fs_api_buffer_size, | 
					
						
							|  |  |  | 				(dma_addr_t *)&a->ppfs_api_buffer, | 
					
						
							|  |  |  | 				GFP_KERNEL); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!a->fs_api_buffer) | 
					
						
							|  |  |  | 		return -ENOMEM; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (off > a->fs_api_buffer_size) | 
					
						
							|  |  |  | 		return 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (count + off > a->fs_api_buffer_size) | 
					
						
							|  |  |  | 		count = a->fs_api_buffer_size - off; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (count < 1) | 
					
						
							|  |  |  | 		return 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	memcpy(a->fs_api_buffer + off, buf, count); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return count; | 
					
						
							|  |  |  | } |