blob: f950d6e85b5f4fa4f5f3664a48e5ba05d3c28d65 [file] [log] [blame]
/* 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 */