| /* filexfer.c |
| * |
| * Copyright © 2013 - 2013 UNISYS CORPORATION |
| * All rights reserved. |
| * |
| * 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, GOOD TITLE or |
| * NON INFRINGEMENT. See the GNU General Public License for more |
| * details. |
| */ |
| |
| /* Code here-in is the "glue" that connects controlvm messages with the |
| * sparfilexfer driver, which is used to transfer file contents as payload |
| * across the controlvm channel. |
| */ |
| |
| #include "globals.h" |
| #include "controlvm.h" |
| #include "visorchipset.h" |
| #include "filexfer.h" |
| |
| #ifdef ENABLE_SPARFILEXFER /* sparfilexfer kernel module enabled in build */ |
| #include "sparfilexfer.h" |
| |
| /* Driver-global memory */ |
| static LIST_HEAD(Request_list); /* list of struct any_request *, via |
| * req_list memb */ |
| |
| /* lock for above pool for allocation of any_request structs, and pool |
| * name; note that kmem_cache_create requires that we keep the storage |
| * for the pool name for the life of the pool |
| */ |
| static DEFINE_SPINLOCK(Request_list_lock); |
| |
| static struct kmem_cache *Request_memory_pool; |
| static const char Request_memory_pool_name[] = "filexfer_request_pool"; |
| size_t Caller_req_context_bytes = 0; /* passed to filexfer_constructor() */ |
| |
| /* This structure defines a single controlvm GETFILE conversation, which |
| * consists of a single controlvm request message and 1 or more controlvm |
| * response messages. |
| */ |
| struct getfile_request { |
| CONTROLVM_MESSAGE_HEADER controlvm_header; |
| atomic_t buffers_in_use; |
| GET_CONTIGUOUS_CONTROLVM_PAYLOAD_FUNC get_contiguous_controlvm_payload; |
| CONTROLVM_RESPOND_WITH_PAYLOAD_FUNC controlvm_respond_with_payload; |
| }; |
| |
| /* This structure defines a single controlvm PUTFILE conversation, which |
| * consists of a single controlvm request with a filename, and additional |
| * controlvm messages with file data. |
| */ |
| struct putfile_request { |
| GET_CONTROLVM_FILEDATA_FUNC get_controlvm_filedata; |
| CONTROLVM_RESPOND_FUNC controlvm_end_putFile; |
| }; |
| |
| /* This structure defines a single file transfer operation, which can either |
| * be a GETFILE or PUTFILE. |
| */ |
| struct any_request { |
| struct list_head req_list; |
| ulong2 file_request_number; |
| ulong2 data_sequence_number; |
| TRANSMITFILE_DUMP_FUNC dump_func; |
| BOOL is_get; |
| union { |
| struct getfile_request get; |
| struct putfile_request put; |
| }; |
| /* Size of caller_context_data will be |
| * <Caller_req_context_bytes> bytes. I aligned this because I |
| * am paranoid about what happens when an arbitrary data |
| * structure with unknown alignment requirements gets copied |
| * here. I want caller_context_data to be aligned to the |
| * coarsest possible alignment boundary that could be required |
| * for any user data structure. |
| */ |
| u8 caller_context_data[1] __aligned(sizeof(ulong2)); |
| }; |
| |
| /* |
| * Links the any_request into the global list of allocated requests |
| * (<Request_list>). |
| */ |
| static void |
| unit_tracking_create(struct list_head *dev_list_link) |
| { |
| unsigned long flags; |
| spin_lock_irqsave(&Request_list_lock, flags); |
| list_add(dev_list_link, &Request_list); |
| spin_unlock_irqrestore(&Request_list_lock, flags); |
| } |
| |
| /* Unlinks a any_request from the global list (<Request_list>). |
| */ |
| static void |
| unit_tracking_destroy(struct list_head *dev_list_link) |
| { |
| unsigned long flags; |
| spin_lock_irqsave(&Request_list_lock, flags); |
| list_del(dev_list_link); |
| spin_unlock_irqrestore(&Request_list_lock, flags); |
| } |
| |
| /* Allocate memory for and return a new any_request struct, and |
| * link it to the global list of outstanding requests. |
| */ |
| static struct any_request * |
| alloc_request(char *fn, int ln) |
| { |
| struct any_request *req = (struct any_request *) |
| (visorchipset_cache_alloc(Request_memory_pool, |
| FALSE, |
| fn, ln)); |
| if (!req) |
| return NULL; |
| memset(req, 0, sizeof(struct any_request) + Caller_req_context_bytes); |
| unit_tracking_create(&req->req_list); |
| return req; |
| } |
| |
| /* Book-end for alloc_request(). |
| */ |
| static void |
| free_request(struct any_request *req, char *fn, int ln) |
| { |
| unit_tracking_destroy(&req->req_list); |
| visorchipset_cache_free(Request_memory_pool, req, fn, ln); |
| } |
| |
| /* Constructor for filexfer.o. |
| */ |
| int |
| filexfer_constructor(size_t req_context_bytes) |
| { |
| int rc = -1; |
| |
| Caller_req_context_bytes = req_context_bytes; |
| Request_memory_pool = |
| kmem_cache_create(Request_memory_pool_name, |
| sizeof(struct any_request) + |
| Caller_req_context_bytes, |
| 0, SLAB_HWCACHE_ALIGN, NULL); |
| if (!Request_memory_pool) { |
| LOGERR("failed to alloc Request_memory_pool"); |
| rc = -ENOMEM; |
| goto Away; |
| } |
| rc = 0; |
| Away: |
| if (rc < 0) { |
| if (Request_memory_pool) { |
| kmem_cache_destroy(Request_memory_pool); |
| Request_memory_pool = NULL; |
| } |
| } |
| return rc; |
| } |
| |
| /* Destructor for filexfer.o. |
| */ |
| void |
| filexfer_destructor(void) |
| { |
| if (Request_memory_pool) { |
| kmem_cache_destroy(Request_memory_pool); |
| Request_memory_pool = NULL; |
| } |
| } |
| |
| /* This function will obtain an available chunk from the controlvm payload area, |
| * store the size in bytes of the chunk in <actual_size>, and return a pointer |
| * to the chunk. The function is passed to the sparfilexfer driver, which calls |
| * it whenever payload space is required to copy file data into. |
| */ |
| static void * |
| get_empty_bucket_for_getfile_data(void *context, |
| ulong min_size, ulong max_size, |
| ulong *actual_size) |
| { |
| void *bucket; |
| struct any_request *req = (struct any_request *) context; |
| |
| if (!req->is_get) { |
| LOGERR("%s - unexpected call", __func__); |
| return NULL; |
| } |
| bucket = (*req->get.get_contiguous_controlvm_payload) |
| (min_size, max_size, actual_size); |
| if (bucket != NULL) { |
| atomic_inc(&req->get.buffers_in_use); |
| DBGINF("%s - sent %lu-byte buffer", __func__, *actual_size); |
| } |
| return bucket; |
| } |
| |
| /* This function will send a controlvm response with data in the payload |
| * (whose space was obtained with get_empty_bucket_for_getfile_data). The |
| * function is passed to the sparfilexfer driver, which calls it whenever it |
| * wants to send file data back across the controlvm channel. |
| */ |
| static int |
| send_full_getfile_data_bucket(void *context, void *bucket, |
| ulong bucket_actual_size, ulong bucket_used_size) |
| { |
| struct any_request *req = (struct any_request *) context; |
| |
| if (!req->is_get) { |
| LOGERR("%s - unexpected call", __func__); |
| return 0; |
| } |
| DBGINF("sending buffer for %lu/%lu", |
| bucket_used_size, bucket_actual_size); |
| if (!(*req->get.controlvm_respond_with_payload) |
| (&req->get.controlvm_header, |
| req->file_request_number, |
| req->data_sequence_number++, |
| 0, bucket, bucket_actual_size, bucket_used_size, TRUE)) |
| atomic_dec(&req->get.buffers_in_use); |
| return 0; |
| } |
| |
| /* This function will send a controlvm response indicating the end of a |
| * GETFILE transfer. The function is passed to the sparfilexfer driver. |
| */ |
| static void |
| send_end_of_getfile_data(void *context, int status) |
| { |
| struct any_request *req = (struct any_request *) context; |
| if (!req->is_get) { |
| LOGERR("%s - unexpected call", __func__); |
| return; |
| } |
| LOGINF("status=%d", status); |
| (*req->get.controlvm_respond_with_payload) |
| (&req->get.controlvm_header, |
| req->file_request_number, |
| req->data_sequence_number++, status, NULL, 0, 0, FALSE); |
| free_request(req, __FILE__, __LINE__); |
| module_put(THIS_MODULE); |
| } |
| |
| /* This function supplies data for a PUTFILE transfer. |
| * The function is passed to the sparfilexfer driver. |
| */ |
| static int |
| get_putfile_data(void *context, void *pbuf, size_t bufsize, |
| BOOL buf_is_userspace, size_t *bytes_transferred) |
| { |
| struct any_request *req = (struct any_request *) context; |
| if (req->is_get) { |
| LOGERR("%s - unexpected call", __func__); |
| return -1; |
| } |
| return (*req->put.get_controlvm_filedata) (&req->caller_context_data[0], |
| pbuf, bufsize, |
| buf_is_userspace, |
| bytes_transferred); |
| } |
| |
| /* This function is called to indicate the end of a PUTFILE transfer. |
| * The function is passed to the sparfilexfer driver. |
| */ |
| static void |
| end_putfile(void *context, int status) |
| { |
| struct any_request *req = (struct any_request *) context; |
| if (req->is_get) { |
| LOGERR("%s - unexpected call", __func__); |
| return; |
| } |
| (*req->put.controlvm_end_putFile) (&req->caller_context_data[0], |
| status); |
| free_request(req, __FILE__, __LINE__); |
| module_put(THIS_MODULE); |
| } |
| |
| /* Refer to filexfer.h for description. */ |
| BOOL |
| filexfer_getFile(CONTROLVM_MESSAGE_HEADER *msgHdr, |
| ulong2 file_request_number, |
| uint uplink_index, |
| uint disk_index, |
| char *file_name, |
| GET_CONTIGUOUS_CONTROLVM_PAYLOAD_FUNC |
| get_contiguous_controlvm_payload, |
| CONTROLVM_RESPOND_WITH_PAYLOAD_FUNC |
| controlvm_respond_with_payload, |
| TRANSMITFILE_DUMP_FUNC dump_func) |
| { |
| BOOL use_count_up = FALSE; |
| BOOL failed = TRUE; |
| struct any_request *req = alloc_request(__FILE__, __LINE__); |
| |
| if (!req) { |
| LOGERR("allocation of any_request failed"); |
| goto Away; |
| } |
| /* We need to increment this module's use count because we're handing |
| * off pointers to functions within this module to be used by |
| * another module. |
| */ |
| __module_get(THIS_MODULE); |
| use_count_up = TRUE; |
| req->is_get = TRUE; |
| req->file_request_number = file_request_number; |
| req->data_sequence_number = 0; |
| req->dump_func = dump_func; |
| req->get.controlvm_header = *msgHdr; |
| atomic_set(&req->get.buffers_in_use, 0); |
| req->get.get_contiguous_controlvm_payload = |
| get_contiguous_controlvm_payload; |
| req->get.controlvm_respond_with_payload = |
| controlvm_respond_with_payload; |
| if (sparfilexfer_local2remote(req, /* context, passed to |
| * callback funcs */ |
| file_name, |
| file_request_number, |
| uplink_index, |
| disk_index, |
| get_empty_bucket_for_getfile_data, |
| send_full_getfile_data_bucket, |
| send_end_of_getfile_data) < 0) { |
| LOGERR("sparfilexfer_local2remote failed"); |
| goto Away; |
| } |
| failed = FALSE; |
| Away: |
| if (failed) { |
| if (use_count_up) { |
| module_put(THIS_MODULE); |
| use_count_up = FALSE; |
| } |
| if (req) { |
| free_request(req, __FILE__, __LINE__); |
| req = NULL; |
| } |
| return FALSE; |
| } else { |
| return TRUE; |
| /* success; send callbacks will be called for responses */ |
| } |
| } |
| |
| /* Refer to filexfer.h for description. */ |
| void * |
| filexfer_putFile(CONTROLVM_MESSAGE_HEADER *msgHdr, |
| ulong2 file_request_number, |
| uint uplink_index, |
| uint disk_index, |
| char *file_name, |
| TRANSMITFILE_INIT_CONTEXT_FUNC init_context, |
| GET_CONTROLVM_FILEDATA_FUNC get_controlvm_filedata, |
| CONTROLVM_RESPOND_FUNC controlvm_end_putFile, |
| TRANSMITFILE_DUMP_FUNC dump_func) |
| { |
| BOOL use_count_up = FALSE; |
| BOOL failed = TRUE; |
| struct any_request *req = alloc_request(__FILE__, __LINE__); |
| void *caller_ctx = NULL; |
| |
| if (!req) { |
| LOGERR("allocation of any_request failed"); |
| goto Away; |
| } |
| caller_ctx = (void *) (&(req->caller_context_data[0])); |
| /* We need to increment this module's use count because we're handing |
| * off pointers to functions within this module to be used by |
| * another module. |
| */ |
| __module_get(THIS_MODULE); |
| use_count_up = TRUE; |
| req->is_get = FALSE; |
| req->file_request_number = file_request_number; |
| req->data_sequence_number = 0; |
| req->dump_func = dump_func; |
| req->put.get_controlvm_filedata = get_controlvm_filedata; |
| req->put.controlvm_end_putFile = controlvm_end_putFile; |
| (*init_context) (caller_ctx, msgHdr, file_request_number); |
| if (sparfilexfer_remote2local(req, /* context, passed to |
| * callback funcs */ |
| file_name, |
| file_request_number, |
| uplink_index, |
| disk_index, |
| get_putfile_data, end_putfile) < 0) { |
| LOGERR("sparfilexfer_remote2local failed"); |
| goto Away; |
| } |
| failed = FALSE; |
| Away: |
| if (failed) { |
| if (use_count_up) { |
| module_put(THIS_MODULE); |
| use_count_up = FALSE; |
| } |
| if (req) { |
| free_request(req, __FILE__, __LINE__); |
| req = NULL; |
| } |
| return NULL; |
| } else { |
| return caller_ctx; |
| /* success; callbacks will be called for responses */ |
| } |
| } |
| |
| static void |
| dump_get_request(struct seq_file *f, struct getfile_request *getreq) |
| { |
| seq_printf(f, " buffers_in_use=%d\n", |
| atomic_read(&getreq->buffers_in_use)); |
| } |
| |
| static void |
| dump_put_request(struct seq_file *f, struct putfile_request *putreq) |
| { |
| } |
| |
| static void |
| dump_request(struct seq_file *f, struct any_request *req) |
| { |
| seq_printf(f, "* %s id=%llu seq=%llu\n", |
| ((req->is_get) ? "Get" : "Put"), |
| req->file_request_number, req->data_sequence_number); |
| if (req->is_get) |
| dump_get_request(f, &req->get); |
| else |
| dump_put_request(f, &req->put); |
| if (req->dump_func) |
| (*req->dump_func) (f, &(req->caller_context_data[0]), " "); |
| } |
| |
| void |
| filexfer_dump(struct seq_file *f) |
| { |
| ulong flags; |
| struct list_head *entry; |
| |
| seq_puts(f, "Outstanding TRANSMIT_FILE requests:\n"); |
| spin_lock_irqsave(&Request_list_lock, flags); |
| list_for_each(entry, &Request_list) { |
| struct any_request *req; |
| req = list_entry(entry, struct any_request, req_list); |
| dump_request(f, req); |
| } |
| spin_unlock_irqrestore(&Request_list_lock, flags); |
| } |
| |
| #else /* ifdef ENABLE_SPARFILEXFER */ |
| int |
| filexfer_constructor(size_t req_context_bytes) |
| { |
| return 0; /* success */ |
| } |
| |
| void |
| filexfer_destructor(void) |
| { |
| } |
| |
| BOOL |
| filexfer_getFile(CONTROLVM_MESSAGE_HEADER *msgHdr, |
| u64 file_request_number, |
| uint uplink_index, |
| uint disk_index, |
| char *file_name, |
| GET_CONTIGUOUS_CONTROLVM_PAYLOAD_FUNC |
| get_contiguous_controlvm_payload, |
| CONTROLVM_RESPOND_WITH_PAYLOAD_FUNC |
| controlvm_respond_with_payload, |
| TRANSMITFILE_DUMP_FUNC dump_func) |
| { |
| /* since no sparfilexfer module exists to call, we just fail */ |
| return FALSE; |
| } |
| |
| void * |
| filexfer_putFile(CONTROLVM_MESSAGE_HEADER *msgHdr, |
| u64 file_request_number, |
| uint uplink_index, |
| uint disk_index, |
| char *file_name, |
| TRANSMITFILE_INIT_CONTEXT_FUNC init_context, |
| GET_CONTROLVM_FILEDATA_FUNC get_controlvm_filedata, |
| CONTROLVM_RESPOND_FUNC controlvm_end_putFile, |
| TRANSMITFILE_DUMP_FUNC dump_func) |
| { |
| /* since no sparfilexfer module exists to call, we just fail */ |
| return NULL; |
| } |
| |
| void |
| filexfer_dump(struct seq_file *f) |
| { |
| } |
| |
| #endif /* ifdef ENABLE_SPARFILEXFER */ |