162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * hvcserver.c
462306a36Sopenharmony_ci * Copyright (C) 2004 Ryan S Arnold, IBM Corporation
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * PPC64 virtual I/O console server support.
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/kernel.h>
1062306a36Sopenharmony_ci#include <linux/list.h>
1162306a36Sopenharmony_ci#include <linux/module.h>
1262306a36Sopenharmony_ci#include <linux/slab.h>
1362306a36Sopenharmony_ci#include <linux/string.h>
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#include <asm/hvcall.h>
1662306a36Sopenharmony_ci#include <asm/hvcserver.h>
1762306a36Sopenharmony_ci#include <asm/io.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#define HVCS_ARCH_VERSION "1.0.0"
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ciMODULE_AUTHOR("Ryan S. Arnold <rsa@us.ibm.com>");
2262306a36Sopenharmony_ciMODULE_DESCRIPTION("IBM hvcs ppc64 API");
2362306a36Sopenharmony_ciMODULE_LICENSE("GPL");
2462306a36Sopenharmony_ciMODULE_VERSION(HVCS_ARCH_VERSION);
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci/*
2762306a36Sopenharmony_ci * Convert arch specific return codes into relevant errnos.  The hvcs
2862306a36Sopenharmony_ci * functions aren't performance sensitive, so this conversion isn't an
2962306a36Sopenharmony_ci * issue.
3062306a36Sopenharmony_ci */
3162306a36Sopenharmony_cistatic int hvcs_convert(long to_convert)
3262306a36Sopenharmony_ci{
3362306a36Sopenharmony_ci	switch (to_convert) {
3462306a36Sopenharmony_ci		case H_SUCCESS:
3562306a36Sopenharmony_ci			return 0;
3662306a36Sopenharmony_ci		case H_PARAMETER:
3762306a36Sopenharmony_ci			return -EINVAL;
3862306a36Sopenharmony_ci		case H_HARDWARE:
3962306a36Sopenharmony_ci			return -EIO;
4062306a36Sopenharmony_ci		case H_BUSY:
4162306a36Sopenharmony_ci		case H_LONG_BUSY_ORDER_1_MSEC:
4262306a36Sopenharmony_ci		case H_LONG_BUSY_ORDER_10_MSEC:
4362306a36Sopenharmony_ci		case H_LONG_BUSY_ORDER_100_MSEC:
4462306a36Sopenharmony_ci		case H_LONG_BUSY_ORDER_1_SEC:
4562306a36Sopenharmony_ci		case H_LONG_BUSY_ORDER_10_SEC:
4662306a36Sopenharmony_ci		case H_LONG_BUSY_ORDER_100_SEC:
4762306a36Sopenharmony_ci			return -EBUSY;
4862306a36Sopenharmony_ci		case H_FUNCTION:
4962306a36Sopenharmony_ci		default:
5062306a36Sopenharmony_ci			return -EPERM;
5162306a36Sopenharmony_ci	}
5262306a36Sopenharmony_ci}
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci/**
5562306a36Sopenharmony_ci * hvcs_free_partner_info - free pi allocated by hvcs_get_partner_info
5662306a36Sopenharmony_ci * @head: list_head pointer for an allocated list of partner info structs to
5762306a36Sopenharmony_ci *	free.
5862306a36Sopenharmony_ci *
5962306a36Sopenharmony_ci * This function is used to free the partner info list that was returned by
6062306a36Sopenharmony_ci * calling hvcs_get_partner_info().
6162306a36Sopenharmony_ci */
6262306a36Sopenharmony_ciint hvcs_free_partner_info(struct list_head *head)
6362306a36Sopenharmony_ci{
6462306a36Sopenharmony_ci	struct hvcs_partner_info *pi;
6562306a36Sopenharmony_ci	struct list_head *element;
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	if (!head)
6862306a36Sopenharmony_ci		return -EINVAL;
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	while (!list_empty(head)) {
7162306a36Sopenharmony_ci		element = head->next;
7262306a36Sopenharmony_ci		pi = list_entry(element, struct hvcs_partner_info, node);
7362306a36Sopenharmony_ci		list_del(element);
7462306a36Sopenharmony_ci		kfree(pi);
7562306a36Sopenharmony_ci	}
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	return 0;
7862306a36Sopenharmony_ci}
7962306a36Sopenharmony_ciEXPORT_SYMBOL(hvcs_free_partner_info);
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci/* Helper function for hvcs_get_partner_info */
8262306a36Sopenharmony_cistatic int hvcs_next_partner(uint32_t unit_address,
8362306a36Sopenharmony_ci		unsigned long last_p_partition_ID,
8462306a36Sopenharmony_ci		unsigned long last_p_unit_address, unsigned long *pi_buff)
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci{
8762306a36Sopenharmony_ci	long retval;
8862306a36Sopenharmony_ci	retval = plpar_hcall_norets(H_VTERM_PARTNER_INFO, unit_address,
8962306a36Sopenharmony_ci			last_p_partition_ID,
9062306a36Sopenharmony_ci				last_p_unit_address, virt_to_phys(pi_buff));
9162306a36Sopenharmony_ci	return hvcs_convert(retval);
9262306a36Sopenharmony_ci}
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci/**
9562306a36Sopenharmony_ci * hvcs_get_partner_info - Get all of the partner info for a vty-server adapter
9662306a36Sopenharmony_ci * @unit_address: The unit_address of the vty-server adapter for which this
9762306a36Sopenharmony_ci *	function is fetching partner info.
9862306a36Sopenharmony_ci * @head: An initialized list_head pointer to an empty list to use to return the
9962306a36Sopenharmony_ci *	list of partner info fetched from the hypervisor to the caller.
10062306a36Sopenharmony_ci * @pi_buff: A page sized buffer pre-allocated prior to calling this function
10162306a36Sopenharmony_ci *	that is to be used to be used by firmware as an iterator to keep track
10262306a36Sopenharmony_ci *	of the partner info retrieval.
10362306a36Sopenharmony_ci *
10462306a36Sopenharmony_ci * This function returns non-zero on success, or if there is no partner info.
10562306a36Sopenharmony_ci *
10662306a36Sopenharmony_ci * The pi_buff is pre-allocated prior to calling this function because this
10762306a36Sopenharmony_ci * function may be called with a spin_lock held and kmalloc of a page is not
10862306a36Sopenharmony_ci * recommended as GFP_ATOMIC.
10962306a36Sopenharmony_ci *
11062306a36Sopenharmony_ci * The first long of this buffer is used to store a partner unit address.  The
11162306a36Sopenharmony_ci * second long is used to store a partner partition ID and starting at
11262306a36Sopenharmony_ci * pi_buff[2] is the 79 character Converged Location Code (diff size than the
11362306a36Sopenharmony_ci * unsigned longs, hence the casting mumbo jumbo you see later).
11462306a36Sopenharmony_ci *
11562306a36Sopenharmony_ci * Invocation of this function should always be followed by an invocation of
11662306a36Sopenharmony_ci * hvcs_free_partner_info() using a pointer to the SAME list head instance
11762306a36Sopenharmony_ci * that was passed as a parameter to this function.
11862306a36Sopenharmony_ci */
11962306a36Sopenharmony_ciint hvcs_get_partner_info(uint32_t unit_address, struct list_head *head,
12062306a36Sopenharmony_ci		unsigned long *pi_buff)
12162306a36Sopenharmony_ci{
12262306a36Sopenharmony_ci	/*
12362306a36Sopenharmony_ci	 * Dealt with as longs because of the hcall interface even though the
12462306a36Sopenharmony_ci	 * values are uint32_t.
12562306a36Sopenharmony_ci	 */
12662306a36Sopenharmony_ci	unsigned long	last_p_partition_ID;
12762306a36Sopenharmony_ci	unsigned long	last_p_unit_address;
12862306a36Sopenharmony_ci	struct hvcs_partner_info *next_partner_info = NULL;
12962306a36Sopenharmony_ci	int more = 1;
13062306a36Sopenharmony_ci	int retval;
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	/* invalid parameters */
13362306a36Sopenharmony_ci	if (!head || !pi_buff)
13462306a36Sopenharmony_ci		return -EINVAL;
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	memset(pi_buff, 0x00, PAGE_SIZE);
13762306a36Sopenharmony_ci	last_p_partition_ID = last_p_unit_address = ~0UL;
13862306a36Sopenharmony_ci	INIT_LIST_HEAD(head);
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	do {
14162306a36Sopenharmony_ci		retval = hvcs_next_partner(unit_address, last_p_partition_ID,
14262306a36Sopenharmony_ci				last_p_unit_address, pi_buff);
14362306a36Sopenharmony_ci		if (retval) {
14462306a36Sopenharmony_ci			/*
14562306a36Sopenharmony_ci			 * Don't indicate that we've failed if we have
14662306a36Sopenharmony_ci			 * any list elements.
14762306a36Sopenharmony_ci			 */
14862306a36Sopenharmony_ci			if (!list_empty(head))
14962306a36Sopenharmony_ci				return 0;
15062306a36Sopenharmony_ci			return retval;
15162306a36Sopenharmony_ci		}
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci		last_p_partition_ID = be64_to_cpu(pi_buff[0]);
15462306a36Sopenharmony_ci		last_p_unit_address = be64_to_cpu(pi_buff[1]);
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci		/* This indicates that there are no further partners */
15762306a36Sopenharmony_ci		if (last_p_partition_ID == ~0UL
15862306a36Sopenharmony_ci				&& last_p_unit_address == ~0UL)
15962306a36Sopenharmony_ci			break;
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci		/* This is a very small struct and will be freed soon in
16262306a36Sopenharmony_ci		 * hvcs_free_partner_info(). */
16362306a36Sopenharmony_ci		next_partner_info = kmalloc(sizeof(struct hvcs_partner_info),
16462306a36Sopenharmony_ci				GFP_ATOMIC);
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci		if (!next_partner_info) {
16762306a36Sopenharmony_ci			printk(KERN_WARNING "HVCONSOLE: kmalloc() failed to"
16862306a36Sopenharmony_ci				" allocate partner info struct.\n");
16962306a36Sopenharmony_ci			hvcs_free_partner_info(head);
17062306a36Sopenharmony_ci			return -ENOMEM;
17162306a36Sopenharmony_ci		}
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci		next_partner_info->unit_address
17462306a36Sopenharmony_ci			= (unsigned int)last_p_unit_address;
17562306a36Sopenharmony_ci		next_partner_info->partition_ID
17662306a36Sopenharmony_ci			= (unsigned int)last_p_partition_ID;
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci		/* copy the Null-term char too */
17962306a36Sopenharmony_ci		strscpy(&next_partner_info->location_code[0],
18062306a36Sopenharmony_ci			(char *)&pi_buff[2],
18162306a36Sopenharmony_ci			sizeof(next_partner_info->location_code));
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci		list_add_tail(&(next_partner_info->node), head);
18462306a36Sopenharmony_ci		next_partner_info = NULL;
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	} while (more);
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci	return 0;
18962306a36Sopenharmony_ci}
19062306a36Sopenharmony_ciEXPORT_SYMBOL(hvcs_get_partner_info);
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci/**
19362306a36Sopenharmony_ci * hvcs_register_connection - establish a connection between this vty-server and
19462306a36Sopenharmony_ci *	a vty.
19562306a36Sopenharmony_ci * @unit_address: The unit address of the vty-server adapter that is to be
19662306a36Sopenharmony_ci *	establish a connection.
19762306a36Sopenharmony_ci * @p_partition_ID: The partition ID of the vty adapter that is to be connected.
19862306a36Sopenharmony_ci * @p_unit_address: The unit address of the vty adapter to which the vty-server
19962306a36Sopenharmony_ci *	is to be connected.
20062306a36Sopenharmony_ci *
20162306a36Sopenharmony_ci * If this function is called once and -EINVAL is returned it may
20262306a36Sopenharmony_ci * indicate that the partner info needs to be refreshed for the
20362306a36Sopenharmony_ci * target unit address at which point the caller must invoke
20462306a36Sopenharmony_ci * hvcs_get_partner_info() and then call this function again.  If,
20562306a36Sopenharmony_ci * for a second time, -EINVAL is returned then it indicates that
20662306a36Sopenharmony_ci * there is probably already a partner connection registered to a
20762306a36Sopenharmony_ci * different vty-server adapter.  It is also possible that a second
20862306a36Sopenharmony_ci * -EINVAL may indicate that one of the parms is not valid, for
20962306a36Sopenharmony_ci * instance if the link was removed between the vty-server adapter
21062306a36Sopenharmony_ci * and the vty adapter that you are trying to open.  Don't shoot the
21162306a36Sopenharmony_ci * messenger.  Firmware implemented it this way.
21262306a36Sopenharmony_ci */
21362306a36Sopenharmony_ciint hvcs_register_connection( uint32_t unit_address,
21462306a36Sopenharmony_ci		uint32_t p_partition_ID, uint32_t p_unit_address)
21562306a36Sopenharmony_ci{
21662306a36Sopenharmony_ci	long retval;
21762306a36Sopenharmony_ci	retval = plpar_hcall_norets(H_REGISTER_VTERM, unit_address,
21862306a36Sopenharmony_ci				p_partition_ID, p_unit_address);
21962306a36Sopenharmony_ci	return hvcs_convert(retval);
22062306a36Sopenharmony_ci}
22162306a36Sopenharmony_ciEXPORT_SYMBOL(hvcs_register_connection);
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci/**
22462306a36Sopenharmony_ci * hvcs_free_connection - free the connection between a vty-server and vty
22562306a36Sopenharmony_ci * @unit_address: The unit address of the vty-server that is to have its
22662306a36Sopenharmony_ci *	connection severed.
22762306a36Sopenharmony_ci *
22862306a36Sopenharmony_ci * This function is used to free the partner connection between a vty-server
22962306a36Sopenharmony_ci * adapter and a vty adapter.
23062306a36Sopenharmony_ci *
23162306a36Sopenharmony_ci * If -EBUSY is returned continue to call this function until 0 is returned.
23262306a36Sopenharmony_ci */
23362306a36Sopenharmony_ciint hvcs_free_connection(uint32_t unit_address)
23462306a36Sopenharmony_ci{
23562306a36Sopenharmony_ci	long retval;
23662306a36Sopenharmony_ci	retval = plpar_hcall_norets(H_FREE_VTERM, unit_address);
23762306a36Sopenharmony_ci	return hvcs_convert(retval);
23862306a36Sopenharmony_ci}
23962306a36Sopenharmony_ciEXPORT_SYMBOL(hvcs_free_connection);
240