mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
239 lines
7.1 KiB
239 lines
7.1 KiB
// SPDX-License-Identifier: GPL-2.0-or-later |
|
/* |
|
* hvcserver.c |
|
* Copyright (C) 2004 Ryan S Arnold, IBM Corporation |
|
* |
|
* PPC64 virtual I/O console server support. |
|
*/ |
|
|
|
#include <linux/kernel.h> |
|
#include <linux/list.h> |
|
#include <linux/module.h> |
|
#include <linux/slab.h> |
|
#include <linux/string.h> |
|
|
|
#include <asm/hvcall.h> |
|
#include <asm/hvcserver.h> |
|
#include <asm/io.h> |
|
|
|
#define HVCS_ARCH_VERSION "1.0.0" |
|
|
|
MODULE_AUTHOR("Ryan S. Arnold <[email protected]>"); |
|
MODULE_DESCRIPTION("IBM hvcs ppc64 API"); |
|
MODULE_LICENSE("GPL"); |
|
MODULE_VERSION(HVCS_ARCH_VERSION); |
|
|
|
/* |
|
* Convert arch specific return codes into relevant errnos. The hvcs |
|
* functions aren't performance sensitive, so this conversion isn't an |
|
* issue. |
|
*/ |
|
static int hvcs_convert(long to_convert) |
|
{ |
|
switch (to_convert) { |
|
case H_SUCCESS: |
|
return 0; |
|
case H_PARAMETER: |
|
return -EINVAL; |
|
case H_HARDWARE: |
|
return -EIO; |
|
case H_BUSY: |
|
case H_LONG_BUSY_ORDER_1_MSEC: |
|
case H_LONG_BUSY_ORDER_10_MSEC: |
|
case H_LONG_BUSY_ORDER_100_MSEC: |
|
case H_LONG_BUSY_ORDER_1_SEC: |
|
case H_LONG_BUSY_ORDER_10_SEC: |
|
case H_LONG_BUSY_ORDER_100_SEC: |
|
return -EBUSY; |
|
case H_FUNCTION: |
|
default: |
|
return -EPERM; |
|
} |
|
} |
|
|
|
/** |
|
* hvcs_free_partner_info - free pi allocated by hvcs_get_partner_info |
|
* @head: list_head pointer for an allocated list of partner info structs to |
|
* free. |
|
* |
|
* This function is used to free the partner info list that was returned by |
|
* calling hvcs_get_partner_info(). |
|
*/ |
|
int hvcs_free_partner_info(struct list_head *head) |
|
{ |
|
struct hvcs_partner_info *pi; |
|
struct list_head *element; |
|
|
|
if (!head) |
|
return -EINVAL; |
|
|
|
while (!list_empty(head)) { |
|
element = head->next; |
|
pi = list_entry(element, struct hvcs_partner_info, node); |
|
list_del(element); |
|
kfree(pi); |
|
} |
|
|
|
return 0; |
|
} |
|
EXPORT_SYMBOL(hvcs_free_partner_info); |
|
|
|
/* Helper function for hvcs_get_partner_info */ |
|
static int hvcs_next_partner(uint32_t unit_address, |
|
unsigned long last_p_partition_ID, |
|
unsigned long last_p_unit_address, unsigned long *pi_buff) |
|
|
|
{ |
|
long retval; |
|
retval = plpar_hcall_norets(H_VTERM_PARTNER_INFO, unit_address, |
|
last_p_partition_ID, |
|
last_p_unit_address, virt_to_phys(pi_buff)); |
|
return hvcs_convert(retval); |
|
} |
|
|
|
/** |
|
* hvcs_get_partner_info - Get all of the partner info for a vty-server adapter |
|
* @unit_address: The unit_address of the vty-server adapter for which this |
|
* function is fetching partner info. |
|
* @head: An initialized list_head pointer to an empty list to use to return the |
|
* list of partner info fetched from the hypervisor to the caller. |
|
* @pi_buff: A page sized buffer pre-allocated prior to calling this function |
|
* that is to be used to be used by firmware as an iterator to keep track |
|
* of the partner info retrieval. |
|
* |
|
* This function returns non-zero on success, or if there is no partner info. |
|
* |
|
* The pi_buff is pre-allocated prior to calling this function because this |
|
* function may be called with a spin_lock held and kmalloc of a page is not |
|
* recommended as GFP_ATOMIC. |
|
* |
|
* The first long of this buffer is used to store a partner unit address. The |
|
* second long is used to store a partner partition ID and starting at |
|
* pi_buff[2] is the 79 character Converged Location Code (diff size than the |
|
* unsigned longs, hence the casting mumbo jumbo you see later). |
|
* |
|
* Invocation of this function should always be followed by an invocation of |
|
* hvcs_free_partner_info() using a pointer to the SAME list head instance |
|
* that was passed as a parameter to this function. |
|
*/ |
|
int hvcs_get_partner_info(uint32_t unit_address, struct list_head *head, |
|
unsigned long *pi_buff) |
|
{ |
|
/* |
|
* Dealt with as longs because of the hcall interface even though the |
|
* values are uint32_t. |
|
*/ |
|
unsigned long last_p_partition_ID; |
|
unsigned long last_p_unit_address; |
|
struct hvcs_partner_info *next_partner_info = NULL; |
|
int more = 1; |
|
int retval; |
|
|
|
/* invalid parameters */ |
|
if (!head || !pi_buff) |
|
return -EINVAL; |
|
|
|
memset(pi_buff, 0x00, PAGE_SIZE); |
|
last_p_partition_ID = last_p_unit_address = ~0UL; |
|
INIT_LIST_HEAD(head); |
|
|
|
do { |
|
retval = hvcs_next_partner(unit_address, last_p_partition_ID, |
|
last_p_unit_address, pi_buff); |
|
if (retval) { |
|
/* |
|
* Don't indicate that we've failed if we have |
|
* any list elements. |
|
*/ |
|
if (!list_empty(head)) |
|
return 0; |
|
return retval; |
|
} |
|
|
|
last_p_partition_ID = be64_to_cpu(pi_buff[0]); |
|
last_p_unit_address = be64_to_cpu(pi_buff[1]); |
|
|
|
/* This indicates that there are no further partners */ |
|
if (last_p_partition_ID == ~0UL |
|
&& last_p_unit_address == ~0UL) |
|
break; |
|
|
|
/* This is a very small struct and will be freed soon in |
|
* hvcs_free_partner_info(). */ |
|
next_partner_info = kmalloc(sizeof(struct hvcs_partner_info), |
|
GFP_ATOMIC); |
|
|
|
if (!next_partner_info) { |
|
printk(KERN_WARNING "HVCONSOLE: kmalloc() failed to" |
|
" allocate partner info struct.\n"); |
|
hvcs_free_partner_info(head); |
|
return -ENOMEM; |
|
} |
|
|
|
next_partner_info->unit_address |
|
= (unsigned int)last_p_unit_address; |
|
next_partner_info->partition_ID |
|
= (unsigned int)last_p_partition_ID; |
|
|
|
/* copy the Null-term char too */ |
|
strlcpy(&next_partner_info->location_code[0], |
|
(char *)&pi_buff[2], |
|
sizeof(next_partner_info->location_code)); |
|
|
|
list_add_tail(&(next_partner_info->node), head); |
|
next_partner_info = NULL; |
|
|
|
} while (more); |
|
|
|
return 0; |
|
} |
|
EXPORT_SYMBOL(hvcs_get_partner_info); |
|
|
|
/** |
|
* hvcs_register_connection - establish a connection between this vty-server and |
|
* a vty. |
|
* @unit_address: The unit address of the vty-server adapter that is to be |
|
* establish a connection. |
|
* @p_partition_ID: The partition ID of the vty adapter that is to be connected. |
|
* @p_unit_address: The unit address of the vty adapter to which the vty-server |
|
* is to be connected. |
|
* |
|
* If this function is called once and -EINVAL is returned it may |
|
* indicate that the partner info needs to be refreshed for the |
|
* target unit address at which point the caller must invoke |
|
* hvcs_get_partner_info() and then call this function again. If, |
|
* for a second time, -EINVAL is returned then it indicates that |
|
* there is probably already a partner connection registered to a |
|
* different vty-server adapter. It is also possible that a second |
|
* -EINVAL may indicate that one of the parms is not valid, for |
|
* instance if the link was removed between the vty-server adapter |
|
* and the vty adapter that you are trying to open. Don't shoot the |
|
* messenger. Firmware implemented it this way. |
|
*/ |
|
int hvcs_register_connection( uint32_t unit_address, |
|
uint32_t p_partition_ID, uint32_t p_unit_address) |
|
{ |
|
long retval; |
|
retval = plpar_hcall_norets(H_REGISTER_VTERM, unit_address, |
|
p_partition_ID, p_unit_address); |
|
return hvcs_convert(retval); |
|
} |
|
EXPORT_SYMBOL(hvcs_register_connection); |
|
|
|
/** |
|
* hvcs_free_connection - free the connection between a vty-server and vty |
|
* @unit_address: The unit address of the vty-server that is to have its |
|
* connection severed. |
|
* |
|
* This function is used to free the partner connection between a vty-server |
|
* adapter and a vty adapter. |
|
* |
|
* If -EBUSY is returned continue to call this function until 0 is returned. |
|
*/ |
|
int hvcs_free_connection(uint32_t unit_address) |
|
{ |
|
long retval; |
|
retval = plpar_hcall_norets(H_FREE_VTERM, unit_address); |
|
return hvcs_convert(retval); |
|
} |
|
EXPORT_SYMBOL(hvcs_free_connection);
|
|
|