diff mbox series

[v3,1/2] x86: Add basic support for the Congatec CGEB BIOS interface

Message ID 20240808183527.3950120-2-mstrodl@csh.rit.edu
State Superseded
Headers show
Series Add support for Congatec CGEB BIOS interface | expand

Commit Message

Mary Strodl Aug. 8, 2024, 6:35 p.m. UTC
The Congatec CGEB is a BIOS interface found on some Congatec x86
modules. It provides access to on board peripherals like I2C busses
and watchdogs. This driver contains the basic support for accessing
the CGEB interface and registers the child devices.

Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
Signed-off-by: Christian Gmeiner <christian.gmeiner@gmail.com>
Signed-off-by: Mary Strodl <mstrodl@csh.rit.edu>
---
 drivers/mfd/Kconfig               |   10 +
 drivers/mfd/Makefile              |    1 +
 drivers/mfd/congatec-cgeb.c       | 1138 +++++++++++++++++++++++++++++
 include/linux/mfd/congatec-cgeb.h |  112 +++
 include/uapi/linux/connector.h    |    4 +-
 5 files changed, 1264 insertions(+), 1 deletion(-)
 create mode 100644 drivers/mfd/congatec-cgeb.c
 create mode 100644 include/linux/mfd/congatec-cgeb.h

Comments

Andi Shyti Aug. 9, 2024, 12:46 a.m. UTC | #1
Hi Mary,

...

> --- /dev/null
> +++ b/drivers/mfd/congatec-cgeb.c
> @@ -0,0 +1,1138 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * CGEB driver
> + *
> + * Copyright (c) 2024 Mary Strodl
> + *
> + * Based on code from Congatec AG and Sascha Hauer
> + *
> + * CGEB is a BIOS interface found on congatech modules. It consists of
> + * code found in the BIOS memory map which is called in a ioctl like
> + * fashion. This file contains the basic driver for this interface
> + * which provides access to the GCEB interface and registers the child
> + * devices like I2C busses and watchdogs.
> + *
> + * 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; version 2 of the License.
> + *
> + * 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.

If you use the SPDX identifier you don't need to mention the GPL
parts here.

> + */
> +#include <linux/kernel.h>
> +#include <linux/string.h>
> +#include <linux/module.h>
> +#include <linux/sched.h>
> +#include <linux/io.h>
> +#include <linux/string.h>
> +#include <linux/errno.h>
> +#include <linux/kernel.h>
> +#include <linux/slab.h>
> +#include <linux/vmalloc.h>
> +#include <linux/mm.h>
> +#include <linux/delay.h>
> +#include <linux/platform_device.h>
> +#include <linux/connector.h>
> +#include <linux/mfd/congatec-cgeb.h>
> +#include <linux/completion.h>
> +
> +#include <generated/autoconf.h>

the include files are normally alphabetically sorted.

> +#pragma pack(push, 4)
> +
> +struct cgeb_low_desc {
> +	char magic[8];          /* descriptor magic string */
> +	u16 size;               /* size of this descriptor */
> +	u16 reserved;
> +	char bios_name[8];      /* BIOS name and revision "ppppRvvv" */
> +	u32 hi_desc_phys_addr;  /* phys addr of the high descriptor, can be 0 */
> +};
> +
> +/* CGEB High Descriptor located in 0xfff00000-0xffffffff */
> +#ifdef CONFIG_X86_64
> +#define CGEB_HD_MAGIC "$CGEBQD$"
> +#else
> +#define CGEB_HD_MAGIC "$CGEBHD$"
> +#endif
> +
> +struct cgeb_high_desc {
> +	char magic[8];          /* descriptor magic string */
> +	u16 size;               /* size of this descriptor */
> +	u16 reserved;
> +	u32 data_size;          /* CGEB data area size */
> +	u32 code_size;          /* CGEB code area size */
> +	u32 entry_rel;          /* CGEB entry point relative to start */
> +};
> +
> +struct cgeb_far_ptr {
> +	void __user *off;
> +	u16 seg;
> +	u16 pad;
> +};
> +
> +struct cgeb_fps {
> +	u32 size;               /* size of the parameter structure */
> +	u32 fct;                /* function number */
> +	struct cgeb_far_ptr data;       /* CGEB data area */
> +	void __user *cont;      /* private continuation pointer */
> +	void __user *subfps;    /* private sub function parameter
> +				 * structure pointer
> +				 */
> +	void __user *subfct;    /* sub function pointer */
> +	u32 status;             /* result codes of the function */
> +	u32 unit;               /* unit number or type */
> +	u32 pars[4];            /* input parameters */
> +	u32 rets[2];            /* return parameters */
> +	void __user *iptr;      /* input pointer */
> +	void __user *optr;      /* output pointer */
> +};

...

> +struct cgeb_map_mem {
> +	void *phys;             /* physical address */
> +	u32 size;               /* size in bytes */
> +	struct cgeb_far_ptr virt;
> +};
> +
> +struct cgeb_map_mem_list {
> +	u32 count;              /* number of memory map entries */
> +	struct cgeb_map_mem entries[];
> +};
> +
> +struct cgeb_boardinfo {
> +	u32 size;
> +	u32 flags;
> +	u32 classes;
> +	u32 primary_class;
> +	char board[CGOS_BOARD_MAX_SIZE_ID_STRING];
> +	/* optional */
> +	char vendor[CGOS_BOARD_MAX_SIZE_ID_STRING];
> +};
> +
> +struct cgeb_i2c_info {
> +	u32 size;
> +	u32 type;
> +	u32 frequency;
> +	u32 maxFrequency;
> +};

...

> +struct cgeb_board_data {
> +	void __user *data;
> +	void __user *code;
> +	size_t code_size;
> +	u16 ds;
> +	struct cgeb_map_mem_list *map_mem;
> +	void __user *map_mem_user;
> +	struct platform_device **devices;
> +	int num_devices;
> +
> +	#ifdef CONFIG_X86_64
> +	void (*entry)(void*, struct cgeb_fps *, struct cgeb_fps *, void*);
> +	#else
> +	/*
> +	 * entry points to a bimodal C style function that expects a far pointer
> +	 * to a fps. If cs is 0 then it does a near return, otherwise a far
> +	 * return. If we ever need a far return then we must not pass cs at all.
> +	 * parameters are removed by the caller.
> +	 */
> +	void __attribute__((regparm(0)))(*entry)(unsigned short,
> +			  struct cgeb_fps *, unsigned short);
> +	#endif
> +};
> +
> +struct cgeb_call_user {
> +	void *optr;
> +	size_t size;
> +	void *callback_data;
> +	int (*callback)(void __user *optr, void *koptr, void *data);
> +};
> +
> +enum cgeb_msg_type {
> +	CGEB_MSG_ACK = 0,
> +	CGEB_MSG_ERROR,
> +	CGEB_MSG_FPS,
> +	CGEB_MSG_MAPPED,
> +	CGEB_MSG_MAP,
> +	CGEB_MSG_CODE,
> +	CGEB_MSG_ALLOC,
> +	CGEB_MSG_ALLOC_CODE,
> +	CGEB_MSG_FREE,
> +	CGEB_MSG_MUNMAP,
> +	CGEB_MSG_CALL,
> +	CGEB_MSG_PING,
> +};
> +
> +struct cgeb_msg {
> +	enum cgeb_msg_type type;
> +	union {
> +		struct cgeb_msg_mapped {
> +			void __user *virt;
> +		} mapped;
> +		struct cgeb_msg_fps {
> +			size_t optr_size;
> +			void __user *optr;
> +			struct cgeb_fps fps;
> +		} fps;
> +		struct cgeb_msg_code {
> +			size_t length;
> +			uint32_t entry_rel;
> +			void __user *data;
> +		} code;
> +		struct cgeb_msg_map {
> +			uint32_t phys;
> +			size_t size;
> +		} map;
> +	};
> +};
> +
> +static char cgeb_helper_path[PATH_MAX] = "/sbin/cgeb-helper";
> +
> +static struct cb_id cgeb_cn_id = {
> +	.idx = CN_IDX_CGEB,
> +	.val = CN_VAL_CGEB
> +};
> +
> +enum cgeb_request_state {
> +	CGEB_REQ_IDLE = 0,
> +	CGEB_REQ_ACTIVE,
> +	CGEB_REQ_DONE,
> +};
> +
> +static DEFINE_MUTEX(cgeb_lock);
> +struct cgeb_request {
> +	struct completion done;
> +	struct cgeb_msg *out;
> +	enum cgeb_request_state busy;
> +	int ack;
> +	int (*callback)(struct cgeb_msg *msg, void *user);
> +	void *user;
> +};
> +
> +#define CGEB_REQUEST_MAX 16
> +static struct cgeb_request cgeb_requests[CGEB_REQUEST_MAX];
> +
> +struct cgeb_after_alloc_data {
> +	void *kernel;
> +	void __user **user;
> +	size_t length;
> +};
> +
> +struct cgeb_map_data {
> +	void __user *user_list;
> +	struct cgeb_board_data *board;
> +};

I'm counting too many global variables here and too many global
structure definitions. It might get a bit hard to follow.

I'd suggest to simplify this part as much as possible and keep
everything in as less structures as possible (ideally just one). 

Please make local as many variables as you can.

...

> +static int cgeb_request(struct cgeb_msg msg, struct cgeb_msg *out,
> +			int (*callback)(struct cgeb_msg*, void*), void *user)
> +{

...

> +	if (req->busy) {
> +		mutex_unlock(&cgeb_lock);
> +		err = -EBUSY;
> +		goto out;

just return -EBUSY

> +	}
> +	wrapper->seq = seq;
> +	req->busy = CGEB_REQ_ACTIVE;
> +	req->ack = wrapper->ack;
> +	req->out = out;
> +	req->callback = callback;
> +	req->user = user;
> +
> +	err = cn_netlink_send(wrapper, 0, 0, GFP_KERNEL);
> +	if (err == -ESRCH) {
> +		err = cgeb_helper_start();
> +		if (err) {
> +			pr_err("failed to execute %s\n", cgeb_helper_path);
> +			pr_err("make sure the cgeb helper is executable\n");
> +		} else {
> +			do {
> +				err = cn_netlink_send(wrapper, 0, 0,
> +						      GFP_KERNEL);
> +				if (err == -ENOBUFS)
> +					err = 0;
> +				if (err == -ESRCH)
> +					msleep(30);
> +			} while (err == -ESRCH && ++retries < 5);

when you use constants like "5", you need to give it an
explanation, either as a define (the name speaks by itself) or a
comment. We don't want people asking "why 5?" :-)

> +		}
> +	} else if (err == -ENOBUFS)
> +		err = 0;

based on the coding style this should have been:

	if (err == -ESRCH) {
		...
	} else if (err == -ENOBUFS) {
		err = 0;
	}

You either use brackets for every if/else/else fi or for no one.

> +	kfree(wrapper);
> +
> +	if (++seq >= CGEB_REQUEST_MAX)
> +		seq = 0;
> +
> +	mutex_unlock(&cgeb_lock);
> +
> +	if (err)
> +		goto out;

just return err

> +
> +	/* Wait for a response to the request */
> +	err = wait_for_completion_interruptible_timeout(
> +		&req->done, msecs_to_jiffies(20000));
> +	if (err == 0) {
> +		pr_err("CGEB: Timed out running request of type %d!\n",
> +		       msg.type);
> +		err = -ETIMEDOUT;

just "return -ETIMEDOUT;" and you can cleanup the code below

> +	} else if (err > 0)
> +		err = 0;

brackets!

> +	if (err)
> +		goto out;
> + 
> +	mutex_lock(&cgeb_lock);
> +
> +	if (req->busy != CGEB_REQ_DONE) {
> +		pr_err("CGEB: BUG: Request is in a bad state?\n");
> +		err = -EINVAL;
> +	}
> +
> +	req->busy = CGEB_REQ_IDLE;
> +	mutex_unlock(&cgeb_lock);
> +out:
> +	return err;

I don't see any need for the out: label

> +}
> +
> +static void cgeb_cn_callback(struct cn_msg *msg, struct netlink_skb_parms *nsp)
> +{
> +	struct cgeb_request *req;
> +
> +	if (!capable(CAP_SYS_ADMIN))
> +		return;
> +
> +	if (msg->seq >= CGEB_REQUEST_MAX) {
> +		pr_err("CGEB: Impossible sequence number: %d! Ignoring\n",
> +		       msg->seq);
> +		return;
> +	}
> +
> +	mutex_lock(&cgeb_lock);
> +	req = &cgeb_requests[msg->seq];
> +
> +	if (!req->busy || req->ack != msg->ack) {
> +		pr_err("CGEB: Bad response to request %d! Ignoring\n",
> +		       msg->seq);
> +		mutex_unlock(&cgeb_lock);
> +		return;
> +	}
> +
> +	if (msg->len != sizeof(*req->out)) {
> +		pr_err("CGEB: Bad response size for request %d!\n", msg->seq);
> +		mutex_unlock(&cgeb_lock);
> +		return;

here it can be useful to have a goto statement:

	goto out;
	...
	out:
		mutex_unlock(...);
		return;

Andi
Mary Strodl Aug. 12, 2024, 11:45 a.m. UTC | #2
Hi Andi,

On Fri, Aug 09, 2024 at 01:46:44AM +0100, Andi Shyti wrote:
> If you use the SPDX identifier you don't need to mention the GPL
> parts here.

Gotcha, will remove the GPL blurb in next series

> the include files are normally alphabetically sorted.

Will-do!

> I'm counting too many global variables here and too many global
> structure definitions. It might get a bit hard to follow.
> 
> I'd suggest to simplify this part as much as possible and keep
> everything in as less structures as possible (ideally just one). 
> 
> Please make local as many variables as you can.

I don't disagree with you here. Unfortunately everything between
the pack(push, 4) and pack(pop) needs to stay, since those are
the BIOS's ABI. I'd be open to putting them in a header file or
something though, but I wasn't sure what convention was on stuff like that.

What do you think? I think moving the BIOS ABI and maybe the over-the-wire
types (struct cgeb_request and friends) out would probably make it much
easier to follow.

> just return -EBUSY
Will-do!

> when you use constants like "5", you need to give it an
> explanation, either as a define (the name speaks by itself) or a
> comment. We don't want people asking "why 5?" :-)
I genuinely don't remember how this loop got into it. I'll see if it
behaves well without the loop.

> You either use brackets for every if/else/else fi or for no one.
Gotcha. I think I fixed all of them, will be in next series.

> just return err
Will-do!

> 
> > +
> > +	/* Wait for a response to the request */
> > +	err = wait_for_completion_interruptible_timeout(
> > +		&req->done, msecs_to_jiffies(20000));
> > +	if (err == 0) {
> > +		pr_err("CGEB: Timed out running request of type %d!\n",
> > +		       msg.type);
> > +		err = -ETIMEDOUT;
> 
> just "return -ETIMEDOUT;" and you can cleanup the code below
Here's where I landed on that:

/* Wait for a response to the request */
err = wait_for_completion_interruptible_timeout(
	&req->done, msecs_to_jiffies(20000));
if (err == 0) {
	pr_err("CGEB: Timed out running request of type %d!\n",
	       msg.type);
	return -ETIMEDOUT;
} else if (err < 0) {
	return err;
} else {
	err = 0;
}

Unfortunately, I do still need to handle the err < 0 case,
but this is a little cleaner than it was.

> brackets!
Will-do

> I don't see any need for the out: label
Removed

> here it can be useful to have a goto statement:
> 
> 	goto out;
> 	...
> 	out:
> 		mutex_unlock(...);
> 		return;

Will-do!

Thanks again for taking the time to review.
Andi Shyti Aug. 12, 2024, 8:16 p.m. UTC | #3
Hi Mary,

> > I'm counting too many global variables here and too many global
> > structure definitions. It might get a bit hard to follow.
> > 
> > I'd suggest to simplify this part as much as possible and keep
> > everything in as less structures as possible (ideally just one). 
> > 
> > Please make local as many variables as you can.
> 
> I don't disagree with you here. Unfortunately everything between
> the pack(push, 4) and pack(pop) needs to stay, since those are
> the BIOS's ABI. I'd be open to putting them in a header file or
> something though, but I wasn't sure what convention was on stuff like that.
> 
> What do you think? I think moving the BIOS ABI and maybe the over-the-wire
> types (struct cgeb_request and friends) out would probably make it much
> easier to follow.

mine was a guess, actually... if you see that my comment doesn't
fit, feel free to ignore it.

As I said I didn't look in details all the structures and their
dependencies.

> > > +
> > > +	/* Wait for a response to the request */
> > > +	err = wait_for_completion_interruptible_timeout(
> > > +		&req->done, msecs_to_jiffies(20000));
> > > +	if (err == 0) {
> > > +		pr_err("CGEB: Timed out running request of type %d!\n",
> > > +		       msg.type);
> > > +		err = -ETIMEDOUT;
> > 
> > just "return -ETIMEDOUT;" and you can cleanup the code below
> Here's where I landed on that:
> 
> /* Wait for a response to the request */
> err = wait_for_completion_interruptible_timeout(
> 	&req->done, msecs_to_jiffies(20000));
> if (err == 0) {
> 	pr_err("CGEB: Timed out running request of type %d!\n",
> 	       msg.type);
> 	return -ETIMEDOUT;
> } else if (err < 0) {
> 	return err;
> } else {

you don't ened anymore for this else, at the end, but this looks
correct.

Thanks,
Andi
kernel test robot Aug. 13, 2024, 12:26 p.m. UTC | #4
Hi Mary,

kernel test robot noticed the following build warnings:

[auto build test WARNING on lee-mfd/for-mfd-next]
[also build test WARNING on lee-mfd/for-mfd-fixes andi-shyti/i2c/i2c-host akpm-mm/mm-everything linus/master v6.11-rc3 next-20240813]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Mary-Strodl/x86-Add-basic-support-for-the-Congatec-CGEB-BIOS-interface/20240809-075405
base:   https://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd.git for-mfd-next
patch link:    https://lore.kernel.org/r/20240808183527.3950120-2-mstrodl%40csh.rit.edu
patch subject: [PATCH v3 1/2] x86: Add basic support for the Congatec CGEB BIOS interface
config: i386-randconfig-r111-20240813 (https://download.01.org/0day-ci/archive/20240813/202408132055.GF2IYZIB-lkp@intel.com/config)
compiler: gcc-12 (Debian 12.2.0-14) 12.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20240813/202408132055.GF2IYZIB-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202408132055.GF2IYZIB-lkp@intel.com/

sparse warnings: (new ones prefixed by >>)
>> drivers/mfd/congatec-cgeb.c:906:17: sparse: sparse: cast removes address space '__iomem' of expression
   drivers/mfd/congatec-cgeb.c:931:27: sparse: sparse: cast removes address space '__iomem' of expression

vim +/__iomem +906 drivers/mfd/congatec-cgeb.c

   888	
   889	static struct cgeb_board_data *cgeb_open(resource_size_t base, u32 len)
   890	{
   891		u32 dw;
   892		struct cgeb_boardinfo pbi;
   893		struct cgeb_low_desc *low_desc;
   894		struct cgeb_high_desc *high_desc = NULL;
   895		u32 high_desc_phys;
   896		u32 high_desc_len;
   897		void *pcur, *high_desc_virt;
   898		int ret;
   899	
   900		struct cgeb_board_data *board;
   901	
   902		board = kzalloc(sizeof(*board), GFP_KERNEL);
   903		if (!board)
   904			return NULL;
   905	
 > 906		pcur = (void *) ioremap_cache(base, len);
   907		if (!pcur)
   908			goto err_kfree;
   909	
   910		/* look for the CGEB descriptor */
   911		low_desc = cgeb_find_magic(pcur, len, CGEB_LD_MAGIC);
   912		if (!low_desc)
   913			goto err_kfree;
   914	
   915		pr_debug("CGEB: Found CGEB_LD_MAGIC\n");
   916	
   917		if (low_desc->size < sizeof(struct cgeb_low_desc) - sizeof(long))
   918			goto err_kfree;
   919	
   920		if (low_desc->size >= sizeof(struct cgeb_low_desc)
   921				&& low_desc->hi_desc_phys_addr)
   922			high_desc_phys = low_desc->hi_desc_phys_addr;
   923		else
   924			high_desc_phys = 0xfff00000;
   925	
   926		high_desc_len = (unsigned int) -(int)high_desc_phys;
   927	
   928		pr_debug("CGEB: Looking for CGEB hi desc between phys 0x%x and 0x%x\n",
   929			high_desc_phys, -1);
   930	
   931		high_desc_virt = (void *) ioremap_cache(high_desc_phys, high_desc_len);
   932		if (!high_desc_virt)
   933			goto err_kfree;
   934	
   935		pr_debug("CGEB: Looking for CGEB hi desc between virt 0x%p and 0x%p\n",
   936			high_desc_virt, high_desc_virt + high_desc_len - 1);
   937	
   938		high_desc = cgeb_find_magic(high_desc_virt, high_desc_len,
   939						CGEB_HD_MAGIC);
   940		if (!high_desc)
   941			goto err_iounmap;
   942	
   943		pr_debug("CGEB: Found CGEB_HD_MAGIC\n");
   944	
   945		if (high_desc->size < sizeof(struct cgeb_high_desc))
   946			goto err_iounmap;
   947	
   948		pr_debug("CGEB: data_size %u, code_size %u, entry_rel %u\n",
   949			high_desc->data_size, high_desc->code_size, high_desc->entry_rel);
   950	
   951		ret = cgeb_upload_code(high_desc, board);
   952		if (ret) {
   953			pr_err("CGEB: Couldn't upload code to helper: %d\n", ret);
   954			goto err_munmap;
   955		}
   956	
   957		board->ds = get_data_segment();
   958	
   959		ret = cgeb_call_simple(board, CgebGetCgebVersion, 0, NULL, 0, &dw);
   960		if (ret)
   961			goto err_munmap;
   962	
   963		if (CGEB_GET_VERSION_MAJOR(dw) != CGEB_VERSION_MAJOR)
   964			goto err_munmap;
   965	
   966		pr_debug("CGEB: BIOS interface revision: %d.%d\n",
   967				dw >> 16, dw & 0xffff);
   968	
   969		if (high_desc->data_size)
   970			dw = high_desc->data_size;
   971		else
   972			ret = cgeb_call_simple(board, CgebGetDataSize, 0, NULL, 0, &dw);
   973	
   974		if (!ret && dw) {
   975			board->data = cgeb_user_alloc(high_desc->data_size);
   976			if (!board->data)
   977				goto err_munmap;
   978		}
   979	
   980		ret = cgeb_call_simple(board, CgebOpen, 0, NULL, 0, NULL);
   981		if (ret)
   982			goto err_free_data;
   983	
   984		pr_debug("CGEB: Mapping memory\n");
   985		ret = cgeb_map_memory(board);
   986		if (ret)
   987			goto err_free_map;
   988		pr_debug("CGEB: Memory is mapped, getting board info\n");
   989	
   990		ret = cgeb_call_simple(board, CgebBoardGetInfo, 0, &pbi,
   991				       sizeof(pbi), NULL);
   992		if (ret)
   993			goto err_free_map;
   994	
   995		pr_info("CGEB: Board name: %c%c%c%c\n",
   996				pbi.board[0], pbi.board[1],
   997				pbi.board[2], pbi.board[3]);
   998	
   999		iounmap((void __iomem *) high_desc_virt);
  1000	
  1001		return board;
  1002	
  1003	err_free_map:
  1004		cgeb_unmap_memory(board);
  1005	err_free_data:
  1006		cgeb_user_free(board->data);
  1007	err_munmap:
  1008		cgeb_munmap(board->code, board->code_size);
  1009	err_iounmap:
  1010		iounmap((void __iomem *) high_desc_virt);
  1011	err_kfree:
  1012		kfree(board);
  1013		return NULL;
  1014	}
  1015
diff mbox series

Patch

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 266b4f54af60..fa06a9dc34f9 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -1206,6 +1206,16 @@  config MFD_RT5120
 	  is targeted at providing the CPU voltage, memory, I/O and peripheral
 	  power rails in home entertainment devices.
 
+config MFD_CONGATEC_CGEB
+	tristate "Support for the Congatec CGEB BIOS interface"
+	depends on X86
+	help
+	  The Congatec CGEB BIOS interface provides access to onboard
+	  peripherals like I2C busses and watchdogs. additional drivers must be
+	  enabled in order to use the functionality of the device.
+	  Say y or m here if you are using a congatec module with CGEB interface,
+	  otherwise say n.
+
 config MFD_RC5T583
 	bool "Ricoh RC5T583 Power Management system device"
 	depends on I2C=y
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index c66f07edcd0e..38f31841ac88 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -217,6 +217,7 @@  obj-$(CONFIG_MFD_INTEL_LPSS)	+= intel-lpss.o
 obj-$(CONFIG_MFD_INTEL_LPSS_PCI)	+= intel-lpss-pci.o
 obj-$(CONFIG_MFD_INTEL_LPSS_ACPI)	+= intel-lpss-acpi.o
 obj-$(CONFIG_MFD_INTEL_PMC_BXT)	+= intel_pmc_bxt.o
+obj-$(CONFIG_MFD_CONGATEC_CGEB)	+= congatec-cgeb.o
 obj-$(CONFIG_MFD_PALMAS)	+= palmas.o
 obj-$(CONFIG_MFD_VIPERBOARD)    += viperboard.o
 obj-$(CONFIG_MFD_NTXEC)		+= ntxec.o
diff --git a/drivers/mfd/congatec-cgeb.c b/drivers/mfd/congatec-cgeb.c
new file mode 100644
index 000000000000..4ad0ce17fe07
--- /dev/null
+++ b/drivers/mfd/congatec-cgeb.c
@@ -0,0 +1,1138 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * CGEB driver
+ *
+ * Copyright (c) 2024 Mary Strodl
+ *
+ * Based on code from Congatec AG and Sascha Hauer
+ *
+ * CGEB is a BIOS interface found on congatech modules. It consists of
+ * code found in the BIOS memory map which is called in a ioctl like
+ * fashion. This file contains the basic driver for this interface
+ * which provides access to the GCEB interface and registers the child
+ * devices like I2C busses and watchdogs.
+ *
+ * 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; version 2 of the License.
+ *
+ * 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.
+ */
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/io.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/mm.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/connector.h>
+#include <linux/mfd/congatec-cgeb.h>
+#include <linux/completion.h>
+
+#include <generated/autoconf.h>
+
+#define CGOS_BOARD_MAX_SIZE_ID_STRING 16
+
+#define CGEB_VERSION_MAJOR 1
+
+#define CGEB_GET_VERSION_MAJOR(v) (((unsigned long)(v)) >> 24)
+
+/* CGEB Low Descriptor located in 0xc0000-0xfffff */
+#define CGEB_LD_MAGIC "$CGEBLD$"
+
+#pragma pack(push, 4)
+
+struct cgeb_low_desc {
+	char magic[8];          /* descriptor magic string */
+	u16 size;               /* size of this descriptor */
+	u16 reserved;
+	char bios_name[8];      /* BIOS name and revision "ppppRvvv" */
+	u32 hi_desc_phys_addr;  /* phys addr of the high descriptor, can be 0 */
+};
+
+/* CGEB High Descriptor located in 0xfff00000-0xffffffff */
+#ifdef CONFIG_X86_64
+#define CGEB_HD_MAGIC "$CGEBQD$"
+#else
+#define CGEB_HD_MAGIC "$CGEBHD$"
+#endif
+
+struct cgeb_high_desc {
+	char magic[8];          /* descriptor magic string */
+	u16 size;               /* size of this descriptor */
+	u16 reserved;
+	u32 data_size;          /* CGEB data area size */
+	u32 code_size;          /* CGEB code area size */
+	u32 entry_rel;          /* CGEB entry point relative to start */
+};
+
+struct cgeb_far_ptr {
+	void __user *off;
+	u16 seg;
+	u16 pad;
+};
+
+struct cgeb_fps {
+	u32 size;               /* size of the parameter structure */
+	u32 fct;                /* function number */
+	struct cgeb_far_ptr data;       /* CGEB data area */
+	void __user *cont;      /* private continuation pointer */
+	void __user *subfps;    /* private sub function parameter
+				 * structure pointer
+				 */
+	void __user *subfct;    /* sub function pointer */
+	u32 status;             /* result codes of the function */
+	u32 unit;               /* unit number or type */
+	u32 pars[4];            /* input parameters */
+	u32 rets[2];            /* return parameters */
+	void __user *iptr;      /* input pointer */
+	void __user *optr;      /* output pointer */
+};
+
+/* continuation status codes */
+#define CGEB_SUCCESS            0
+#define CGEB_NEXT               1
+#define CGEB_DELAY              2
+#define CGEB_NOIRQS             3
+
+#define CGEB_DBG_STR        0x100
+#define CGEB_DBG_HEX        0x101
+#define CGEB_DBG_DEC        0x102
+
+struct cgeb_map_mem {
+	void *phys;             /* physical address */
+	u32 size;               /* size in bytes */
+	struct cgeb_far_ptr virt;
+};
+
+struct cgeb_map_mem_list {
+	u32 count;              /* number of memory map entries */
+	struct cgeb_map_mem entries[];
+};
+
+struct cgeb_boardinfo {
+	u32 size;
+	u32 flags;
+	u32 classes;
+	u32 primary_class;
+	char board[CGOS_BOARD_MAX_SIZE_ID_STRING];
+	/* optional */
+	char vendor[CGOS_BOARD_MAX_SIZE_ID_STRING];
+};
+
+struct cgeb_i2c_info {
+	u32 size;
+	u32 type;
+	u32 frequency;
+	u32 maxFrequency;
+};
+
+#pragma pack(pop)
+
+/* I2C Types */
+#define CGEB_I2C_TYPE_UNKNOWN 0
+#define CGEB_I2C_TYPE_PRIMARY 1
+#define CGEB_I2C_TYPE_SMB     2
+#define CGEB_I2C_TYPE_DDC     3
+#define CGEB_I2C_TYPE_BC      4
+
+struct cgeb_board_data {
+	void __user *data;
+	void __user *code;
+	size_t code_size;
+	u16 ds;
+	struct cgeb_map_mem_list *map_mem;
+	void __user *map_mem_user;
+	struct platform_device **devices;
+	int num_devices;
+
+	#ifdef CONFIG_X86_64
+	void (*entry)(void*, struct cgeb_fps *, struct cgeb_fps *, void*);
+	#else
+	/*
+	 * entry points to a bimodal C style function that expects a far pointer
+	 * to a fps. If cs is 0 then it does a near return, otherwise a far
+	 * return. If we ever need a far return then we must not pass cs at all.
+	 * parameters are removed by the caller.
+	 */
+	void __attribute__((regparm(0)))(*entry)(unsigned short,
+			  struct cgeb_fps *, unsigned short);
+	#endif
+};
+
+struct cgeb_call_user {
+	void *optr;
+	size_t size;
+	void *callback_data;
+	int (*callback)(void __user *optr, void *koptr, void *data);
+};
+
+enum cgeb_msg_type {
+	CGEB_MSG_ACK = 0,
+	CGEB_MSG_ERROR,
+	CGEB_MSG_FPS,
+	CGEB_MSG_MAPPED,
+	CGEB_MSG_MAP,
+	CGEB_MSG_CODE,
+	CGEB_MSG_ALLOC,
+	CGEB_MSG_ALLOC_CODE,
+	CGEB_MSG_FREE,
+	CGEB_MSG_MUNMAP,
+	CGEB_MSG_CALL,
+	CGEB_MSG_PING,
+};
+
+struct cgeb_msg {
+	enum cgeb_msg_type type;
+	union {
+		struct cgeb_msg_mapped {
+			void __user *virt;
+		} mapped;
+		struct cgeb_msg_fps {
+			size_t optr_size;
+			void __user *optr;
+			struct cgeb_fps fps;
+		} fps;
+		struct cgeb_msg_code {
+			size_t length;
+			uint32_t entry_rel;
+			void __user *data;
+		} code;
+		struct cgeb_msg_map {
+			uint32_t phys;
+			size_t size;
+		} map;
+	};
+};
+
+static char cgeb_helper_path[PATH_MAX] = "/sbin/cgeb-helper";
+
+static struct cb_id cgeb_cn_id = {
+	.idx = CN_IDX_CGEB,
+	.val = CN_VAL_CGEB
+};
+
+enum cgeb_request_state {
+	CGEB_REQ_IDLE = 0,
+	CGEB_REQ_ACTIVE,
+	CGEB_REQ_DONE,
+};
+
+static DEFINE_MUTEX(cgeb_lock);
+struct cgeb_request {
+	struct completion done;
+	struct cgeb_msg *out;
+	enum cgeb_request_state busy;
+	int ack;
+	int (*callback)(struct cgeb_msg *msg, void *user);
+	void *user;
+};
+
+#define CGEB_REQUEST_MAX 16
+static struct cgeb_request cgeb_requests[CGEB_REQUEST_MAX];
+
+struct cgeb_after_alloc_data {
+	void *kernel;
+	void __user **user;
+	size_t length;
+};
+
+struct cgeb_map_data {
+	void __user *user_list;
+	struct cgeb_board_data *board;
+};
+
+static int cgeb_helper_start(void)
+{
+	char *envp[] = {
+		NULL,
+	};
+
+	char *argv[] = {
+		cgeb_helper_path,
+		NULL,
+	};
+
+	return call_usermodehelper(cgeb_helper_path, argv, envp, UMH_WAIT_EXEC);
+}
+
+static int cgeb_request(struct cgeb_msg msg, struct cgeb_msg *out,
+			int (*callback)(struct cgeb_msg*, void*), void *user)
+{
+	static int seq;
+	struct cn_msg *wrapper;
+	struct cgeb_request *req;
+	int err, retries = 0;
+
+	wrapper = kzalloc(sizeof(*wrapper) + sizeof(msg), GFP_KERNEL);
+	if (!wrapper)
+		return -ENOMEM;
+
+	memset(wrapper, 0, sizeof(*wrapper));
+	memcpy(&wrapper->id, &cgeb_cn_id, sizeof(cgeb_cn_id));
+
+	wrapper->len = sizeof(msg);
+	wrapper->ack = get_random_u32();
+	memcpy(wrapper + 1, &msg, sizeof(msg));
+
+	mutex_lock(&cgeb_lock);
+
+	req = &cgeb_requests[seq];
+
+	if (req->busy) {
+		mutex_unlock(&cgeb_lock);
+		err = -EBUSY;
+		goto out;
+	}
+	wrapper->seq = seq;
+	req->busy = CGEB_REQ_ACTIVE;
+	req->ack = wrapper->ack;
+	req->out = out;
+	req->callback = callback;
+	req->user = user;
+
+	err = cn_netlink_send(wrapper, 0, 0, GFP_KERNEL);
+	if (err == -ESRCH) {
+		err = cgeb_helper_start();
+		if (err) {
+			pr_err("failed to execute %s\n", cgeb_helper_path);
+			pr_err("make sure the cgeb helper is executable\n");
+		} else {
+			do {
+				err = cn_netlink_send(wrapper, 0, 0,
+						      GFP_KERNEL);
+				if (err == -ENOBUFS)
+					err = 0;
+				if (err == -ESRCH)
+					msleep(30);
+			} while (err == -ESRCH && ++retries < 5);
+		}
+	} else if (err == -ENOBUFS)
+		err = 0;
+
+	kfree(wrapper);
+
+	if (++seq >= CGEB_REQUEST_MAX)
+		seq = 0;
+
+	mutex_unlock(&cgeb_lock);
+
+	if (err)
+		goto out;
+
+	/* Wait for a response to the request */
+	err = wait_for_completion_interruptible_timeout(
+		&req->done, msecs_to_jiffies(20000));
+	if (err == 0) {
+		pr_err("CGEB: Timed out running request of type %d!\n",
+		       msg.type);
+		err = -ETIMEDOUT;
+	} else if (err > 0)
+		err = 0;
+
+	if (err)
+		goto out;
+
+	mutex_lock(&cgeb_lock);
+
+	if (req->busy != CGEB_REQ_DONE) {
+		pr_err("CGEB: BUG: Request is in a bad state?\n");
+		err = -EINVAL;
+	}
+
+	req->busy = CGEB_REQ_IDLE;
+	mutex_unlock(&cgeb_lock);
+out:
+	return err;
+}
+
+static void cgeb_cn_callback(struct cn_msg *msg, struct netlink_skb_parms *nsp)
+{
+	struct cgeb_request *req;
+
+	if (!capable(CAP_SYS_ADMIN))
+		return;
+
+	if (msg->seq >= CGEB_REQUEST_MAX) {
+		pr_err("CGEB: Impossible sequence number: %d! Ignoring\n",
+		       msg->seq);
+		return;
+	}
+
+	mutex_lock(&cgeb_lock);
+	req = &cgeb_requests[msg->seq];
+
+	if (!req->busy || req->ack != msg->ack) {
+		pr_err("CGEB: Bad response to request %d! Ignoring\n",
+		       msg->seq);
+		mutex_unlock(&cgeb_lock);
+		return;
+	}
+
+	if (msg->len != sizeof(*req->out)) {
+		pr_err("CGEB: Bad response size for request %d!\n", msg->seq);
+		mutex_unlock(&cgeb_lock);
+		return;
+	}
+
+	pr_debug("Got a response to request %d!\n", msg->seq);
+
+	memcpy(req->out, &msg->data, sizeof(*req->out));
+
+	req->busy = CGEB_REQ_DONE;
+	mutex_unlock(&cgeb_lock);
+
+	if (req->callback)
+		req->callback(req->out, req->user);
+
+	complete(&req->done);
+}
+
+static int cgeb_copy_to_user(struct cgeb_msg *resp, void *user)
+{
+	struct cgeb_high_desc *high_desc;
+	unsigned long uncopied;
+
+	high_desc = user;
+	uncopied = copy_to_user(resp->code.data, high_desc,
+				high_desc->code_size);
+	if (uncopied) {
+		pr_err("CGEB: Couldn't copy code into userspace! %ld\n",
+		       uncopied);
+		return -ENOMEM;
+	}
+	return 0;
+}
+
+static int cgeb_upload_code(struct cgeb_high_desc *high_desc,
+			    struct cgeb_board_data *board)
+{
+	struct cgeb_msg req = {0}, resp;
+	size_t len = high_desc->code_size;
+	int ret = 0;
+
+	req.type = CGEB_MSG_ALLOC_CODE;
+	req.code.length = len;
+	pr_debug("CGEB: Allocating memory for code\n");
+	ret = cgeb_request(req, &resp, cgeb_copy_to_user, high_desc);
+	if (ret)
+		goto out;
+	if (resp.type != CGEB_MSG_CODE) {
+		pr_err("CGEB: Bad response type for alloc: %d\n", resp.type);
+		ret = -EINVAL;
+		goto out;
+	}
+
+	board->code = resp.code.data;
+	board->code_size = len;
+
+	req.type = CGEB_MSG_CODE;
+	req.code.data = resp.code.data;
+	req.code.entry_rel = high_desc->entry_rel;
+	req.code.length = len;
+
+	pr_debug("CGEB: Uploading code\n");
+	ret = cgeb_request(req, &resp, NULL, NULL);
+
+	if (ret)
+		goto out;
+
+	/* Do stuff with response */
+	if (resp.type != CGEB_MSG_ACK) {
+		pr_err("CGEB: Failed to upload code! Got non-ack response!\n");
+		ret = -EINVAL;
+	}
+
+out:
+	return ret;
+}
+
+static unsigned short get_data_segment(void)
+{
+	unsigned short ret;
+
+#ifdef CONFIG_X86_64
+	ret = 0;
+#else
+	asm volatile("mov %%ds, %0\n"
+			  : "=r"(ret)
+			  :
+			  : "memory"
+	);
+#endif
+
+	return ret;
+}
+
+static int cgeb_after_call(struct cgeb_msg *resp, void *user)
+{
+	int ret = 0;
+	int alloc_size;
+	struct cgeb_call_user *data = user;
+
+	if (!resp->fps.fps.optr)
+		return ret;
+
+	switch (resp->fps.fps.status) {
+	case CGEB_NEXT:
+	case CGEB_NOIRQS:
+	case CGEB_DELAY:
+	case CGEB_DBG_HEX:
+	case CGEB_DBG_DEC:
+		/* These lead to continuations, we don't need their memory */
+		return ret;
+
+	/* Everything else we could need */
+	case CGEB_DBG_STR:
+		data->size = alloc_size = strnlen_user(resp->fps.fps.optr, 1023);
+		if (alloc_size > 1023) {
+			data->size = 1023;
+			alloc_size = data->size + 1;
+		}
+		/* Special case, because these come from program memory */
+		data->optr = kzalloc(alloc_size, GFP_KERNEL);
+		if (!data->optr)
+			return -ENOMEM;
+	}
+
+	ret = copy_from_user(data->optr, resp->fps.fps.optr, data->size);
+
+	if (ret) {
+		pr_err("CGEB: Couldn't copy optr out of userspace! %d\n", ret);
+		ret = -ENOMEM;
+	}
+
+	if (resp->fps.fps.status == CGEB_SUCCESS && data->callback) {
+		data->callback(resp->fps.fps.optr, data->optr,
+			       data->callback_data);
+	}
+
+	return ret;
+}
+
+static int cgeb_after_alloc(struct cgeb_msg *resp, void *user)
+{
+	int ret;
+	struct cgeb_after_alloc_data *data = user;
+
+	ret = copy_to_user(resp->code.data, data->kernel, data->length);
+
+	if (ret) {
+		pr_err("CGEB: Couldn't copy iptr into userspace! %d\n", ret);
+
+		ret = -ENOMEM;
+	}
+
+	*data->user = resp->code.data;
+
+	return ret;
+}
+
+static int cgeb_get_user_ptr(void *kernel, void __user **user, size_t length)
+{
+	struct cgeb_msg req = {0}, resp;
+	struct cgeb_after_alloc_data data;
+
+	data.kernel = kernel;
+	data.user = user;
+	data.length = length;
+
+	req.type = CGEB_MSG_ALLOC;
+	req.code.length = length;
+	return cgeb_request(req, &resp, cgeb_after_alloc, &data);
+}
+
+static void __user *cgeb_user_alloc(size_t length)
+{
+	int ret;
+	struct cgeb_msg req = {0}, resp;
+
+	req.type = CGEB_MSG_ALLOC;
+	req.code.length = length;
+	ret = cgeb_request(req, &resp, NULL, NULL);
+	if (ret)
+		return NULL;
+
+	return resp.code.data;
+}
+
+static int cgeb_user_free(void __user *memory)
+{
+	struct cgeb_msg req = {0}, resp;
+
+	req.type = CGEB_MSG_FREE;
+	req.code.data = memory;
+	return cgeb_request(req, &resp, NULL, NULL);
+}
+
+/*
+ * cgeb_call - invoke CGEB BIOS call.
+ *
+ * @board:     board context data
+ * @p:         CGEB parameters for this call
+ * @fct:       CGEB function code
+ * @return:    0 on success or negative error code on failure.
+ *
+ * Call the CGEB BIOS code with the given parameters.
+ */
+int cgeb_call(struct cgeb_board_data *board,
+	      struct cgeb_function_parameters *p, enum cgeb_function_t fct)
+{
+	struct cgeb_fps *fps;
+	struct cgeb_msg req = {0}, resp;
+	struct cgeb_call_user user_data, user_data_clone;
+	int i;
+	int err;
+	void __user *iptr_user = NULL;
+	void __user *optr_user = NULL;
+
+	if ((p->optr && !p->optr_size) || (p->iptr && !p->iptr_size) ||
+	    (!p->optr && p->optr_size) || (!p->iptr && p->iptr_size)) {
+		pr_warn("CGEB: called with impossible iptr/optr\n");
+		return -EINVAL;
+	}
+
+	memset(&req, 0, sizeof(req));
+	fps = &req.fps.fps;
+
+	fps->size = sizeof(fps);
+	fps->fct = fct;
+	fps->data.off = board->data;
+	fps->data.seg = board->ds;
+	fps->data.pad = 0;
+	fps->status = 0;
+	fps->cont = fps->subfct = fps->subfps = NULL;
+	fps->unit = p->unit;
+	for (i = 0; i < 4; i++)
+		fps->pars[i] = p->pars[i];
+	if (p->iptr) {
+		if (!p->iptr_size)
+			return -EINVAL;
+		err = cgeb_get_user_ptr(p->iptr, &iptr_user, p->iptr_size);
+		fps->iptr = iptr_user;
+		if (err)
+			return err;
+	}
+	req.fps.optr_size = p->optr_size;
+	user_data.optr = p->optr;
+	user_data.size = p->optr_size;
+
+	user_data.callback = p->callback;
+	user_data.callback_data = p->callback_data;
+
+	while (1) {
+		pr_debug("CGEB: CGEB: ->  size %02x, fct %02x, data %04x:%p\n",
+			 fps->size, fps->fct, fps->data.seg, fps->data.off);
+
+		user_data_clone = user_data;
+		req.type = CGEB_MSG_CALL;
+		req.fps.fps = *fps;
+		err = cgeb_request(req, &resp, cgeb_after_call,
+				   &user_data_clone);
+		if (err)
+			goto out;
+		if (resp.type != CGEB_MSG_FPS) {
+			err = -EINVAL;
+			goto out;
+		}
+		fps = &resp.fps.fps;
+		/* Don't allocate another optr */
+		req.fps.optr_size = 0;
+
+		if (resp.fps.optr)
+			optr_user = resp.fps.optr;
+
+		/* DBG_STR causes an allocation, don't overwrite our original */
+		if (fps->status != CGEB_DBG_STR)
+			user_data = user_data_clone;
+
+		pr_debug("CGEB: CGEB: <-  size %02x, fct %02x, data %04x:%p\n",
+			 fps->size, fps->fct, fps->data.seg, fps->data.off);
+
+		switch (fps->status) {
+		case CGEB_SUCCESS:
+			goto out;
+		case CGEB_NEXT:
+			break;  /* simply call again */
+		case CGEB_NOIRQS:
+			__set_current_state(TASK_INTERRUPTIBLE);
+			schedule_timeout(0);
+			break;
+		case CGEB_DELAY:
+			usleep_range(fps->rets[0], fps->rets[0] + 1000);
+			break;
+		case CGEB_DBG_STR:
+			if (fps->optr) {
+				pr_debug("CGEB (dbg): %s\n", (char *)user_data_clone.optr);
+				kfree(user_data_clone.optr);
+			}
+			break;
+		case CGEB_DBG_HEX:
+			pr_debug("CGEB (dbg): 0x%08x\n", fps->rets[0]);
+			break;
+		case CGEB_DBG_DEC:
+			pr_debug("CGEB (dbg): %d\n", fps->rets[0]);
+			break;
+
+		default:
+			/* unknown continuation code */
+			err = -EINVAL;
+			goto out;
+		}
+	}
+out:
+	if (iptr_user)
+		cgeb_user_free(iptr_user);
+	if (optr_user)
+		cgeb_user_free(optr_user);
+	for (i = 0; i < 2; i++)
+		p->rets[i] = fps->rets[i];
+	p->optr = user_data.optr;
+
+	return err;
+}
+EXPORT_SYMBOL_GPL(cgeb_call);
+
+/*
+ * cgeb_call_simple - convenience wrapper for cgeb_call
+ *
+ * @board:     board context data
+ * @p:         CGEB parameters for this call
+ * @fct:       CGEB function code
+ * @return:    0 on success or negative error code on failure.
+ *
+ * Call the CGEB BIOS code with the given parameters.
+ */
+int cgeb_call_simple(struct cgeb_board_data *board,
+		     enum cgeb_function_t fct, u32 unit,
+		     void *optr, size_t optr_size, u32 *result)
+{
+	struct cgeb_function_parameters p;
+	unsigned int ret;
+
+	memset(&p, 0, sizeof(p));
+	p.optr_size = optr_size;
+	p.unit = unit;
+	p.optr = optr;
+
+	ret = cgeb_call(board, &p, fct);
+
+	if (result)
+		*result = p.rets[0];
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(cgeb_call_simple);
+
+static void *cgeb_find_magic(void *_mem, size_t len, char *magic)
+{
+	u32 magic0 = ((u32 *)magic)[0];
+	u32 magic1 = ((u32 *)magic)[1];
+	int i = 0;
+
+	while (i < len) {
+		u32 *mem = _mem + i;
+
+		if (mem[0] == magic0 && mem[1] == magic1)
+			return mem;
+		i += 16;
+	}
+
+	return NULL;
+}
+
+
+static int cgeb_overwrite_map(struct cgeb_msg *req, void *user)
+{
+	struct cgeb_board_data *board = (struct cgeb_board_data *) user;
+	struct cgeb_map_mem_list *pmm = board->map_mem;
+	int err;
+
+	err = copy_to_user(board->map_mem_user, board->map_mem,
+			   sizeof(pmm->entries[0]) * pmm->count + sizeof(*pmm));
+	if (err) {
+		pr_err("CGEB: Couldn't copy map_mem into userspace!\n");
+		return -ENOMEM;
+	}
+	return 0;
+}
+
+static void cgeb_unmap_memory(struct cgeb_board_data *board)
+{
+	struct cgeb_map_mem_list *pmm;
+	struct cgeb_map_mem *pmme;
+	struct cgeb_msg req = {0}, res;
+	unsigned long i;
+
+	if (!board->map_mem)
+		return;
+
+	req.type = CGEB_MSG_MUNMAP;
+
+	pmm = board->map_mem;
+	pmme = pmm->entries;
+	pr_debug("CGEB: Unmapping %d pages\n", pmm->count);
+	for (i = 0; i < pmm->count; i++, pmme++) {
+		pmme->virt.seg = 0;
+		if (pmme->virt.off) {
+			req.code.data = pmme->virt.off;
+			req.code.length = pmme->size;
+			pmme->virt.off = NULL;
+			cgeb_request(req, &res, cgeb_overwrite_map, board);
+		}
+	}
+	board->map_mem_user = NULL;
+	kfree(board->map_mem);
+}
+
+static int cgeb_deref_map(void __user *userspace, void *optr, void *user)
+{
+	struct cgeb_board_data *board = (struct cgeb_board_data *) user;
+	struct cgeb_map_mem_list *list = optr;
+	size_t size;
+	int uncopied;
+
+	size = sizeof(*list) + sizeof(list->entries[0]) * list->count;
+
+	board->map_mem = kzalloc(size, GFP_KERNEL);
+	if (!board->map_mem)
+		return -ENOMEM;
+
+	board->map_mem_user = userspace;
+	uncopied = copy_from_user(board->map_mem, board->map_mem_user, size);
+	if (uncopied) {
+		pr_err("Couldn't copy map_mem from map_mem_user!\n");
+		kfree(board->map_mem);
+		board->map_mem = NULL;
+		return -ENOMEM;
+	}
+	return 0;
+}
+
+
+static int cgeb_map_memory(struct cgeb_board_data *board)
+{
+	struct cgeb_map_mem_list *pmm;
+	struct cgeb_map_mem *pmme;
+	struct cgeb_msg req = {0}, res;
+	struct cgeb_function_parameters fps = {0};
+	int i;
+	int ret;
+
+	fps.optr = &board->map_mem;
+	fps.optr_size = sizeof(*board->map_mem);
+	fps.callback = cgeb_deref_map;
+	fps.callback_data = board;
+	ret = cgeb_call(board, &fps, CgebMapGetMem);
+	if (ret)
+		return ret;
+
+	if (!board->map_mem)
+		return 0;
+
+	pmm = board->map_mem;
+	pmme = pmm->entries;
+
+	req.type = CGEB_MSG_MAP;
+
+	pr_debug("CGEB: Memory Map with %d entries\n", pmm->count);
+
+	for (i = 0; i < pmm->count; i++, pmme++) {
+		pr_debug("CGEB: Memory map entry phys=%p, size=%08x\n",
+				pmme->phys, pmme->size);
+		if (pmme->phys && pmme->size) {
+			/* We only want to look at the lower 32 bits */
+			req.map.phys = (size_t) pmme->phys & 0xffffffff;
+			req.map.size = pmme->size;
+			ret = cgeb_request(req, &res, NULL, NULL);
+			if (ret)
+				return ret;
+			if (res.type != CGEB_MSG_MAPPED) {
+				pr_err("CGEB: Invalid map response!\n");
+				return -EINVAL;
+			}
+			pmme->virt.off = res.mapped.virt;
+		} else {
+			pmme->virt.off = NULL;
+		}
+
+		pmme->virt.seg = (pmme->virt.off) ? board->ds : 0;
+
+		pr_debug("CGEB:   Map phys %p, size %08x, virt %04x:%p\n",
+			pmme->phys, pmme->size, pmme->virt.seg,
+			pmme->virt.off);
+	}
+
+	cgeb_request(req, &res, cgeb_overwrite_map, board);
+	return cgeb_call_simple(board, CgebMapChanged, 0, NULL, 0, NULL);
+}
+
+static void cgeb_munmap(void __user *ptr, size_t size)
+{
+	struct cgeb_msg req = {0}, res;
+
+	req.type = CGEB_MSG_MUNMAP;
+	req.code.data = ptr;
+	req.code.length = size;
+	cgeb_request(req, &res, NULL, NULL);
+}
+
+static struct cgeb_board_data *cgeb_open(resource_size_t base, u32 len)
+{
+	u32 dw;
+	struct cgeb_boardinfo pbi;
+	struct cgeb_low_desc *low_desc;
+	struct cgeb_high_desc *high_desc = NULL;
+	u32 high_desc_phys;
+	u32 high_desc_len;
+	void *pcur, *high_desc_virt;
+	int ret;
+
+	struct cgeb_board_data *board;
+
+	board = kzalloc(sizeof(*board), GFP_KERNEL);
+	if (!board)
+		return NULL;
+
+	pcur = (void *) ioremap_cache(base, len);
+	if (!pcur)
+		goto err_kfree;
+
+	/* look for the CGEB descriptor */
+	low_desc = cgeb_find_magic(pcur, len, CGEB_LD_MAGIC);
+	if (!low_desc)
+		goto err_kfree;
+
+	pr_debug("CGEB: Found CGEB_LD_MAGIC\n");
+
+	if (low_desc->size < sizeof(struct cgeb_low_desc) - sizeof(long))
+		goto err_kfree;
+
+	if (low_desc->size >= sizeof(struct cgeb_low_desc)
+			&& low_desc->hi_desc_phys_addr)
+		high_desc_phys = low_desc->hi_desc_phys_addr;
+	else
+		high_desc_phys = 0xfff00000;
+
+	high_desc_len = (unsigned int) -(int)high_desc_phys;
+
+	pr_debug("CGEB: Looking for CGEB hi desc between phys 0x%x and 0x%x\n",
+		high_desc_phys, -1);
+
+	high_desc_virt = (void *) ioremap_cache(high_desc_phys, high_desc_len);
+	if (!high_desc_virt)
+		goto err_kfree;
+
+	pr_debug("CGEB: Looking for CGEB hi desc between virt 0x%p and 0x%p\n",
+		high_desc_virt, high_desc_virt + high_desc_len - 1);
+
+	high_desc = cgeb_find_magic(high_desc_virt, high_desc_len,
+					CGEB_HD_MAGIC);
+	if (!high_desc)
+		goto err_iounmap;
+
+	pr_debug("CGEB: Found CGEB_HD_MAGIC\n");
+
+	if (high_desc->size < sizeof(struct cgeb_high_desc))
+		goto err_iounmap;
+
+	pr_debug("CGEB: data_size %u, code_size %u, entry_rel %u\n",
+		high_desc->data_size, high_desc->code_size, high_desc->entry_rel);
+
+	ret = cgeb_upload_code(high_desc, board);
+	if (ret) {
+		pr_err("CGEB: Couldn't upload code to helper: %d\n", ret);
+		goto err_munmap;
+	}
+
+	board->ds = get_data_segment();
+
+	ret = cgeb_call_simple(board, CgebGetCgebVersion, 0, NULL, 0, &dw);
+	if (ret)
+		goto err_munmap;
+
+	if (CGEB_GET_VERSION_MAJOR(dw) != CGEB_VERSION_MAJOR)
+		goto err_munmap;
+
+	pr_debug("CGEB: BIOS interface revision: %d.%d\n",
+			dw >> 16, dw & 0xffff);
+
+	if (high_desc->data_size)
+		dw = high_desc->data_size;
+	else
+		ret = cgeb_call_simple(board, CgebGetDataSize, 0, NULL, 0, &dw);
+
+	if (!ret && dw) {
+		board->data = cgeb_user_alloc(high_desc->data_size);
+		if (!board->data)
+			goto err_munmap;
+	}
+
+	ret = cgeb_call_simple(board, CgebOpen, 0, NULL, 0, NULL);
+	if (ret)
+		goto err_free_data;
+
+	pr_debug("CGEB: Mapping memory\n");
+	ret = cgeb_map_memory(board);
+	if (ret)
+		goto err_free_map;
+	pr_debug("CGEB: Memory is mapped, getting board info\n");
+
+	ret = cgeb_call_simple(board, CgebBoardGetInfo, 0, &pbi,
+			       sizeof(pbi), NULL);
+	if (ret)
+		goto err_free_map;
+
+	pr_info("CGEB: Board name: %c%c%c%c\n",
+			pbi.board[0], pbi.board[1],
+			pbi.board[2], pbi.board[3]);
+
+	iounmap((void __iomem *) high_desc_virt);
+
+	return board;
+
+err_free_map:
+	cgeb_unmap_memory(board);
+err_free_data:
+	cgeb_user_free(board->data);
+err_munmap:
+	cgeb_munmap(board->code, board->code_size);
+err_iounmap:
+	iounmap((void __iomem *) high_desc_virt);
+err_kfree:
+	kfree(board);
+	return NULL;
+}
+
+static void cgeb_close(struct cgeb_board_data *board)
+{
+	cgeb_call_simple(board, CgebClose, 0, NULL, 0, NULL);
+
+	cgeb_unmap_memory(board);
+
+	cgeb_user_free(board->data);
+
+	cgeb_munmap(board->code, board->code_size);
+}
+
+static unsigned long cgeb_i2c_get_type(struct cgeb_board_data *brd, int unit)
+{
+	struct cgeb_i2c_info info;
+	int ret;
+
+	ret = cgeb_call_simple(brd, CgebI2CGetInfo, unit, &info,
+			       sizeof(info), NULL);
+	if (ret)
+		return ret;
+	return info.type;
+}
+
+static struct cgeb_board_data *cgeb_board;
+
+static int __init cgeb_init(void)
+{
+	struct cgeb_board_data *board;
+	resource_size_t base;
+	int i, ret, req;
+	struct cgeb_pdata pdata;
+	u32 i2c_count, watchdog_count;
+	int num_devices = 0;
+
+	for (req = 0; req < CGEB_REQUEST_MAX; ++req)
+		init_completion(&cgeb_requests[req].done);
+
+	ret = cn_add_callback(&cgeb_cn_id, "cgeb", cgeb_cn_callback);
+	if (ret)
+		return ret;
+
+	pr_debug("CGEB: Opening board\n");
+	for (base = 0xf0000; base >= 0xc0000; base -= 0x10000) {
+		board = cgeb_open(base, 0x10000);
+		if (board)
+			break;
+	}
+
+	if (!board) {
+		ret = -ENODEV;
+		goto err_no_board;
+	}
+
+	cgeb_board = board;
+
+	pdata.board = board;
+
+	cgeb_call_simple(board, CgebI2CCount, 0, NULL, 0, &i2c_count);
+	cgeb_call_simple(board, CgebWDogCount, 0, NULL, 0, &watchdog_count);
+
+	board->num_devices = i2c_count + watchdog_count;
+	board->devices = kzalloc(sizeof(void *) * (board->num_devices),
+			 GFP_KERNEL);
+	if (!board->devices) {
+		ret = -ENOMEM;
+		goto err_out;
+	}
+
+	for (i = 0; i < i2c_count; i++) {
+		ret = cgeb_i2c_get_type(board, i);
+		if (ret != CGEB_I2C_TYPE_PRIMARY)
+			continue;
+
+		 pdata.unit = i;
+
+		 board->devices[num_devices] =
+			platform_device_register_data(NULL, "cgeb-i2c", i,
+					&pdata, sizeof(pdata));
+		 num_devices++;
+	}
+
+	for (i = 0; i < watchdog_count; i++) {
+		board->devices[num_devices] =
+			platform_device_register_data(NULL, "cgeb-watchdog", i,
+					&pdata, sizeof(pdata));
+		pdata.unit = i;
+
+		num_devices++;
+	}
+
+	return 0;
+err_out:
+	cgeb_close(board);
+	kfree(board);
+
+err_no_board:
+	cn_del_callback(&cgeb_cn_id);
+
+	return ret;
+}
+
+static void __exit cgeb_exit(void)
+{
+	struct cgeb_board_data *board = cgeb_board;
+	int i;
+
+	for (i = 0; i < board->num_devices; i++)
+		if (board->devices[i])
+			platform_device_unregister(board->devices[i]);
+
+	cgeb_close(board);
+
+	kfree(board->devices);
+	kfree(board);
+	cn_del_callback(&cgeb_cn_id);
+}
+
+module_init(cgeb_init);
+module_exit(cgeb_exit);
+
+MODULE_AUTHOR("Mary Strodl <mstrodl@csh.rit.edu>");
+MODULE_DESCRIPTION("CGEB driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/congatec-cgeb.h b/include/linux/mfd/congatec-cgeb.h
new file mode 100644
index 000000000000..dba9444c7547
--- /dev/null
+++ b/include/linux/mfd/congatec-cgeb.h
@@ -0,0 +1,112 @@ 
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __CONGATEC_CGEB_H
+#define __CONGATEC_CGEB_H
+
+/* CGEB interface functions */
+enum cgeb_function_t {
+	CgebGetCgebVersion =            0,
+	CgebGetSysBiosVersion =         1,
+	CgebGetVgaBiosVersion =         2,
+	CgebGetDataSize =               3,
+	CgebOpen =                      4,
+	CgebClose =                     5,
+	CgebMapGetMem =                 6,
+	CgebMapChanged =                7,
+	CgebMapGetPorts =               8,
+	CgebDelayUs =                   9,
+	CgebCgbcReadWrite =             10,
+	CgebCgbcSetControl =            11,
+	CgebCgbcGetInfo =               12,
+	CgebCgbcHandleCommand =         13,
+	CgebBoardGetInfo =              14,
+	CgebBoardGetBootCounter =       15,
+	CgebBoardGetRunningTimeMeter =  16,
+	CgebBoardGetBootErrorLog =      17,
+	CgebVgaCount =                  18,
+	CgebVgaGetInfo =                19,
+	CgebVgaGetContrast =            20,
+	CgebVgaSetContrast =            21,
+	CgebVgaGetContrastEnable =      22,
+	CgebVgaSetContrastEnable =      23,
+	CgebVgaGetBacklight =           24,
+	CgebVgaSetBacklight =           25,
+	CgebVgaGetBacklightEnable =     26,
+	CgebVgaSetBacklightEnable =     27,
+	CgebVgaEndDarkBoot =            28,
+	CgebStorageAreaCount =          29,
+	CgebStorageAreaGetInfo =        30,
+	CgebStorageAreaRead =           31,
+	CgebStorageAreaWrite =          32,
+	CgebStorageAreaErase =          33,
+	CgebStorageAreaEraseStatus =    34,
+	CgebI2CCount =                  35,
+	CgebI2CGetInfo =                36,
+	CgebI2CGetAddrList =            37,
+	CgebI2CTransfer =               38,
+	CgebI2CGetFrequency =           39,
+	CgebI2CSetFrequency =           40,
+	CgebIOCount =                   41,
+	CgebIOGetInfo =                 42,
+	CgebIORead =                    43,
+	CgebIOWrite =                   44,
+	CgebIOGetDirection =            45,
+	CgebIOSetDirection =            46,
+	CgebWDogCount =                 47,
+	CgebWDogGetInfo =               48,
+	CgebWDogTrigger =               49,
+	CgebWDogGetConfig =             50,
+	CgebWDogSetConfig =             51,
+	CgebPerformanceGetCurrent =     52,
+	CgebPerformanceSetCurrent =     53,
+	CgebPerformanceGetPolicyCaps =  54,
+	CgebPerformanceGetPolicy =      55,
+	CgebPerformanceSetPolicy =      56,
+	CgebTemperatureCount =          57,
+	CgebTemperatureGetInfo =        58,
+	CgebTemperatureGetCurrent =     59,
+	CgebTemperatureSetLimits =      60,
+	CgebFanCount =                  61,
+	CgebFanGetInfo =                62,
+	CgebFanGetCurrent =             63,
+	CgebFanSetLimits =              64,
+	CgebVoltageCount =              65,
+	CgebVoltageGetInfo =            66,
+	CgebVoltageGetCurrent =         67,
+	CgebVoltageSetLimits =          68,
+	CgebStorageAreaLock =           69,
+	CgebStorageAreaUnlock =         70,
+	CgebStorageAreaIsLocked =       71,
+};
+
+struct cgeb_function_parameters {
+	u32 unit;               /* unit number or type */
+	u32 pars[4];            /* input parameters */
+	u32 rets[2];            /* return parameters */
+	void *iptr;             /* input pointer */
+	void *optr;             /* output pointer */
+	size_t iptr_size;       /* size of input pointer */
+	size_t optr_size;       /* size of output pointer */
+	void *callback_data;    /* callback data pointer */
+	int (*callback)(void __user *optr, void *koptr, void *data);
+				/* Run when CGEB call finishes with userspace */
+				/* optr, kernel optr, and callback_data */
+};
+
+struct cgeb_board_data;
+
+int cgeb_call(struct cgeb_board_data *board,
+		struct cgeb_function_parameters *p, enum cgeb_function_t fct);
+
+int cgeb_call_simple(struct cgeb_board_data *board,
+		enum cgeb_function_t fct, u32 unit,
+		void *optr, size_t optr_size, u32 *result);
+
+/*
+ * Platform data for child devices
+ */
+struct cgeb_pdata {
+	struct cgeb_board_data *board;
+	int unit;
+};
+
+#endif /* __CONGATEC_CGEB_H */
diff --git a/include/uapi/linux/connector.h b/include/uapi/linux/connector.h
index 5ae131c3f145..f64862bee5ce 100644
--- a/include/uapi/linux/connector.h
+++ b/include/uapi/linux/connector.h
@@ -47,9 +47,11 @@ 
 #define CN_KVP_VAL			0x1	/* queries from the kernel */
 #define CN_VSS_IDX			0xA     /* HyperV VSS */
 #define CN_VSS_VAL			0x1     /* queries from the kernel */
+#define CN_IDX_CGEB			0xB	/* congatec CGEB */
+#define CN_VAL_CGEB			0x1
 
 
-#define CN_NETLINK_USERS		11	/* Highest index + 1 */
+#define CN_NETLINK_USERS		12	/* Highest index + 1 */
 
 /*
  * Maximum connector's message size.