diff mbox series

[V2,08/10] libmctp: Import libmctp library handling MCTP protocol

Message ID 20220315124914.51569-9-clombard@linux.vnet.ibm.com
State Superseded
Headers show
Series Import external libraries for MCTP/PLDM protocols | expand

Commit Message

Christophe Lombard March 15, 2022, 12:49 p.m. UTC
The Management Component Transport Protocol (MCTP) is a protocol defined by
the DMTF Platform Management Component Intercommunications sub-team of the
DMTF Pre-OS Workgroup. MCTP is designed to support communications between
different intelligent hardware components that make up a platform
management subsystem that is provides monitoring and control functions
inside a managed system. DMTF standard "DSP2016"

This library is intended to be a portable implementation of the Management
Component Transport Protocol (MCTP), as defined by DMTF standard "DSP0236",
plus transport binding specifications.
MCTP has been designed to carry multiple types of manageability-related
traffic across a common medium. The base MCTP specifications define
message types for supporting the initialization and configuration of MCTP
itself, and to support vendor-specific messages over MCTP.
Other message types, such as message types to support a Platform Level
Data Model (PLDM).

The source is located here: https://github.com/openbmc/libmctp and use as
is, without any update.

The libmctp code is integrated into the folder ./libmctp as a set of
sources, compilated if the compiler flag CONFIG_PLDM is set.

Signed-off-by: Christophe Lombard <clombard@linux.vnet.ibm.com>
---
 Makefile                 |    2 +
 Makefile.main            |   18 +-
 libmctp/LICENSE          |  547 +++++++++++++++
 libmctp/Makefile.inc     |   22 +
 libmctp/README.skiboot   |   15 +
 libmctp/alloc.c          |   60 ++
 libmctp/astlpc.c         | 1366 ++++++++++++++++++++++++++++++++++++++
 libmctp/config.h         |   59 ++
 libmctp/container_of.h   |    9 +
 libmctp/core.c           |  833 +++++++++++++++++++++++
 libmctp/crc32.c          |   25 +
 libmctp/crc32.h          |    9 +
 libmctp/libmctp-alloc.h  |   12 +
 libmctp/libmctp-astlpc.h |   54 ++
 libmctp/libmctp-cmds.h   |   74 +++
 libmctp/libmctp-log.h    |   24 +
 libmctp/libmctp-serial.h |   38 ++
 libmctp/libmctp.h        |  156 +++++
 libmctp/log.c            |   75 +++
 libmctp/range.h          |   18 +
 libmctp/serial.c         |  359 ++++++++++
 21 files changed, 3774 insertions(+), 1 deletion(-)
 create mode 100644 libmctp/LICENSE
 create mode 100644 libmctp/Makefile.inc
 create mode 100644 libmctp/README.skiboot
 create mode 100644 libmctp/alloc.c
 create mode 100644 libmctp/astlpc.c
 create mode 100644 libmctp/config.h
 create mode 100644 libmctp/container_of.h
 create mode 100644 libmctp/core.c
 create mode 100644 libmctp/crc32.c
 create mode 100644 libmctp/crc32.h
 create mode 100644 libmctp/libmctp-alloc.h
 create mode 100644 libmctp/libmctp-astlpc.h
 create mode 100644 libmctp/libmctp-cmds.h
 create mode 100644 libmctp/libmctp-log.h
 create mode 100644 libmctp/libmctp-serial.h
 create mode 100644 libmctp/libmctp.h
 create mode 100644 libmctp/log.c
 create mode 100644 libmctp/range.h
 create mode 100644 libmctp/serial.c

Comments

Frederic Barrat March 31, 2023, 2:03 p.m. UTC | #1
On 15/03/2022 13:49, Christophe Lombard wrote:
> The Management Component Transport Protocol (MCTP) is a protocol defined by
> the DMTF Platform Management Component Intercommunications sub-team of the
> DMTF Pre-OS Workgroup. MCTP is designed to support communications between
> different intelligent hardware components that make up a platform
> management subsystem that is provides monitoring and control functions
> inside a managed system. DMTF standard "DSP2016"
> 
> This library is intended to be a portable implementation of the Management
> Component Transport Protocol (MCTP), as defined by DMTF standard "DSP0236",
> plus transport binding specifications.
> MCTP has been designed to carry multiple types of manageability-related
> traffic across a common medium. The base MCTP specifications define
> message types for supporting the initialization and configuration of MCTP
> itself, and to support vendor-specific messages over MCTP.
> Other message types, such as message types to support a Platform Level
> Data Model (PLDM).
> 
> The source is located here: https://github.com/openbmc/libmctp and use as
> is, without any update.
> 
> The libmctp code is integrated into the folder ./libmctp as a set of
> sources, compilated if the compiler flag CONFIG_PLDM is set.


Typo "compilated" => "compiled" (also in the next 2 patches)

It may be worth re-synchronizing with latest upstream, since this is now 
one year old?


 > diff --git a/Makefile.main b/Makefile.main
 > index 2a346a6c..738e0857 100644
 > --- a/Makefile.main
 > +++ b/Makefile.main
 > @@ -75,7 +75,7 @@ DBG=-g
 >
 >   CPPFLAGS := -I$(SRC)/include -Iinclude -MMD -include 
$(SRC)/include/config.h
 >   CPPFLAGS += -I$(SRC)/libfdt -I$(SRC)/libflash -I$(SRC)/libxz 
-I$(SRC)/libc/include -I$(SRC)
 > -CPPFLAGS += -I$(SRC)/libpore
 > +CPPFLAGS += -I$(SRC)/libpore -I$(SRC)/libmctp
 >   CPPFLAGS += -D__SKIBOOT__ -nostdinc
 >   CPPFLAGS += -isystem $(shell $(CC) -print-file-name=include)
 >   CPPFLAGS += -DBITS_PER_LONG=64
 > @@ -91,6 +91,13 @@ else
 >   CPPFLAGS += -DHAVE_BIG_ENDIAN
 >   endif
 >
 > +# Also add the asm/byte-order.h style ones used by libmctp
 > +ifeq ($(LITTLE_ENDIAN),1)
 > +CPPFLAGS += -D__LITTLE_ENDIAN_BITFIELD
 > +else
 > +CPPFLAGS += -D__BIG_ENDIAN_BITFIELD
 > +endif
 > +


It would be simpler to merge with the previous "ifeq 
($(LITTLE_ENDIAN),1)" just above, since they are almost similar.


> diff --git a/libmctp/Makefile.inc b/libmctp/Makefile.inc
> new file mode 100644
> index 00000000..24d6fcf5
> --- /dev/null
> +++ b/libmctp/Makefile.inc
> @@ -0,0 +1,22 @@
> +# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
> +# Copyright 2022 IBM Corp.
> +
> +LIBMCTP_DIR ?= libmctp
> +SUBDIRS += $(LIBMCTP_DIR)
> +
> +LIBMCTP_OBJS = crc32.o core.o alloc.o log.o
> +LIBMCTP_BINDINGS ?= astlpc
> +
> +LIBMCTP_OBJS += $(LIBMCTP_BINDINGS:%=%.o)
> +
> +CFLAGS_$(LIBMCTP_DIR)/ = -I$(SRC)/ccan/endian/ \


The "-I$(SRC)/ccan/endian/" is useless, as you now have a 
include/endian.h file, which is in the default path and will also 
include the file found in ccan/


> diff --git a/libmctp/config.h b/libmctp/config.h

You may want to add a note in the commit message that even though the 
config.h file is typically automatically generated by 'configure', it's 
not the case here and it has been carefully edited to match the 
environment provided in the skiboot repo.

   Fred
Christophe Lombard April 4, 2023, 8:30 a.m. UTC | #2
Le 31/03/2023 à 16:03, Frederic Barrat a écrit :
>
> On 15/03/2022 13:49, Christophe Lombard wrote:
>> The Management Component Transport Protocol (MCTP) is a protocol 
>> defined by
>> the DMTF Platform Management Component Intercommunications sub-team 
>> of the
>> DMTF Pre-OS Workgroup. MCTP is designed to support communications 
>> between
>> different intelligent hardware components that make up a platform
>> management subsystem that is provides monitoring and control functions
>> inside a managed system. DMTF standard "DSP2016"
>>
>> This library is intended to be a portable implementation of the 
>> Management
>> Component Transport Protocol (MCTP), as defined by DMTF standard 
>> "DSP0236",
>> plus transport binding specifications.
>> MCTP has been designed to carry multiple types of manageability-related
>> traffic across a common medium. The base MCTP specifications define
>> message types for supporting the initialization and configuration of 
>> MCTP
>> itself, and to support vendor-specific messages over MCTP.
>> Other message types, such as message types to support a Platform Level
>> Data Model (PLDM).
>>
>> The source is located here: https://github.com/openbmc/libmctp and 
>> use as
>> is, without any update.
>>
>> The libmctp code is integrated into the folder ./libmctp as a set of
>> sources, compilated if the compiler flag CONFIG_PLDM is set.
>
>
> Typo "compilated" => "compiled" (also in the next 2 patches)
>
> It may be worth re-synchronizing with latest upstream, since this is 
> now one year old?
>

We should, yes.

Thanks

>
> > diff --git a/Makefile.main b/Makefile.main
> > index 2a346a6c..738e0857 100644
> > --- a/Makefile.main
> > +++ b/Makefile.main
> > @@ -75,7 +75,7 @@ DBG=-g
> >
> >   CPPFLAGS := -I$(SRC)/include -Iinclude -MMD -include 
> $(SRC)/include/config.h
> >   CPPFLAGS += -I$(SRC)/libfdt -I$(SRC)/libflash -I$(SRC)/libxz 
> -I$(SRC)/libc/include -I$(SRC)
> > -CPPFLAGS += -I$(SRC)/libpore
> > +CPPFLAGS += -I$(SRC)/libpore -I$(SRC)/libmctp
> >   CPPFLAGS += -D__SKIBOOT__ -nostdinc
> >   CPPFLAGS += -isystem $(shell $(CC) -print-file-name=include)
> >   CPPFLAGS += -DBITS_PER_LONG=64
> > @@ -91,6 +91,13 @@ else
> >   CPPFLAGS += -DHAVE_BIG_ENDIAN
> >   endif
> >
> > +# Also add the asm/byte-order.h style ones used by libmctp
> > +ifeq ($(LITTLE_ENDIAN),1)
> > +CPPFLAGS += -D__LITTLE_ENDIAN_BITFIELD
> > +else
> > +CPPFLAGS += -D__BIG_ENDIAN_BITFIELD
> > +endif
> > +
>
>
> It would be simpler to merge with the previous "ifeq 
> ($(LITTLE_ENDIAN),1)" just above, since they are almost similar.
>
>
>> diff --git a/libmctp/Makefile.inc b/libmctp/Makefile.inc
>> new file mode 100644
>> index 00000000..24d6fcf5
>> --- /dev/null
>> +++ b/libmctp/Makefile.inc
>> @@ -0,0 +1,22 @@
>> +# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
>> +# Copyright 2022 IBM Corp.
>> +
>> +LIBMCTP_DIR ?= libmctp
>> +SUBDIRS += $(LIBMCTP_DIR)
>> +
>> +LIBMCTP_OBJS = crc32.o core.o alloc.o log.o
>> +LIBMCTP_BINDINGS ?= astlpc
>> +
>> +LIBMCTP_OBJS += $(LIBMCTP_BINDINGS:%=%.o)
>> +
>> +CFLAGS_$(LIBMCTP_DIR)/ = -I$(SRC)/ccan/endian/ \
>
>
> The "-I$(SRC)/ccan/endian/" is useless, as you now have a 
> include/endian.h file, which is in the default path and will also 
> include the file found in ccan/
>
>
>> diff --git a/libmctp/config.h b/libmctp/config.h
>
> You may want to add a note in the commit message that even though the 
> config.h file is typically automatically generated by 'configure', 
> it's not the case here and it has been carefully edited to match the 
> environment provided in the skiboot repo.
>
>   Fred
>
diff mbox series

Patch

diff --git a/Makefile b/Makefile
index 625f212e..23af860d 100644
--- a/Makefile
+++ b/Makefile
@@ -61,6 +61,8 @@  DEAD_CODE_ELIMINATION ?= 0
 CONFIG_FSP?=1
 # Try to build without POWER8 support
 CONFIG_P8?=1
+# Try to build without PLDM code
+CONFIG_PLDM?=0
 
 #
 # Where is the source directory, must be a full path (no ~)
diff --git a/Makefile.main b/Makefile.main
index 2a346a6c..738e0857 100644
--- a/Makefile.main
+++ b/Makefile.main
@@ -75,7 +75,7 @@  DBG=-g
 
 CPPFLAGS := -I$(SRC)/include -Iinclude -MMD -include $(SRC)/include/config.h
 CPPFLAGS += -I$(SRC)/libfdt -I$(SRC)/libflash -I$(SRC)/libxz -I$(SRC)/libc/include -I$(SRC)
-CPPFLAGS += -I$(SRC)/libpore
+CPPFLAGS += -I$(SRC)/libpore -I$(SRC)/libmctp
 CPPFLAGS += -D__SKIBOOT__ -nostdinc
 CPPFLAGS += -isystem $(shell $(CC) -print-file-name=include)
 CPPFLAGS += -DBITS_PER_LONG=64
@@ -91,6 +91,13 @@  else
 CPPFLAGS += -DHAVE_BIG_ENDIAN
 endif
 
+# Also add the asm/byte-order.h style ones used by libmctp
+ifeq ($(LITTLE_ENDIAN),1)
+CPPFLAGS += -D__LITTLE_ENDIAN_BITFIELD
+else
+CPPFLAGS += -D__BIG_ENDIAN_BITFIELD
+endif
+
 ifeq ($(DEBUG),1)
 CPPFLAGS += -DDEBUG -DCCAN_LIST_DEBUG
 endif
@@ -164,6 +171,9 @@  endif
 ifeq ($(CONFIG_P8),1)
 CFLAGS += -DCONFIG_P8=1
 endif
+ifeq ($(CONFIG_PLDM),1)
+CFLAGS += -DCONFIG_PLDM=1
+endif
 
 CFLAGS += $(call try-cflag,$(CC),-Wjump-misses-init) \
 	  $(call try-cflag,$(CC),-Wsuggest-attribute=const) \
@@ -295,6 +305,9 @@  include $(SRC)/libc/Makefile.inc
 include $(SRC)/ccan/Makefile.inc
 include $(SRC)/$(DEVSRC)/Makefile.inc
 include $(SRC)/libstb/Makefile.inc
+ifeq ($(CONFIG_PLDM),1)
+include $(SRC)/libmctp/Makefile.inc
+endif
 
 # hack for travis-ci and coverity
 gard:
@@ -319,6 +332,9 @@  all: $(TARGET).lid.stb $(TARGET).lid.xz.stb
 
 OBJS := $(ASM) $(CORE) $(HW) $(PLATFORMS) $(LIBFDT) $(LIBXZ) $(LIBFLASH) $(LIBSTB)
 OBJS += $(LIBC) $(CCAN) $(DEVSRC_OBJ) $(LIBPORE)
+ifeq ($(CONFIG_PLDM),1)
+OBJS += $(LIBMCTP)
+endif
 OBJS_NO_VER = $(OBJS)
 ALL_OBJS = $(OBJS) version.o
 
diff --git a/libmctp/LICENSE b/libmctp/LICENSE
new file mode 100644
index 00000000..37fc4a76
--- /dev/null
+++ b/libmctp/LICENSE
@@ -0,0 +1,547 @@ 
+libmctp is released under a dual Apache-2.0 OR GPLv2+ license. The
+texts of both licences is included below.
+
+-------------------------------------------------------------------------------
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "{}"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright {yyyy} {name of copyright owner}
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+-------------------------------------------------------------------------------
+
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/libmctp/Makefile.inc b/libmctp/Makefile.inc
new file mode 100644
index 00000000..24d6fcf5
--- /dev/null
+++ b/libmctp/Makefile.inc
@@ -0,0 +1,22 @@ 
+# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+# Copyright 2022 IBM Corp.
+
+LIBMCTP_DIR ?= libmctp
+SUBDIRS += $(LIBMCTP_DIR)
+
+LIBMCTP_OBJS = crc32.o core.o alloc.o log.o
+LIBMCTP_BINDINGS ?= astlpc
+
+LIBMCTP_OBJS += $(LIBMCTP_BINDINGS:%=%.o)
+
+CFLAGS_$(LIBMCTP_DIR)/ = -I$(SRC)/ccan/endian/ \
+			-DHAVE_CONFIG_H \
+			-Wno-error \
+			-Wno-type-limits \
+			-Wno-missing-prototypes \
+			-Wno-missing-declarations \
+			-Wno-suggest-attribute=const
+
+LIBMCTP = $(LIBMCTP_DIR)/built-in.a
+
+$(LIBMCTP): $(LIBMCTP_OBJS:%=$(LIBMCTP_DIR)/%)
diff --git a/libmctp/README.skiboot b/libmctp/README.skiboot
new file mode 100644
index 00000000..d8581a70
--- /dev/null
+++ b/libmctp/README.skiboot
@@ -0,0 +1,15 @@ 
+skiboot/libmctp/ is a minimally modified version of upstream libmctp
+that is distributed with the openbmc project hosted at
+https://github.com/openbmc/libmctp.git
+
+This version is taken from libmctp.git commit f39c38575828 ("core:
+Add TX/RX API that exposes message tag and tag owner ") by copying most
+of files from the libmctp/ directory.
+
+The only modifications from the upstream source are the additions of
+this file, Makefile.inc which has been derived from the original
+Makefile.inc, astlpc.c (compilation error when MCTP_HAVE_FILEIO is
+undefined) and the removal of several unnecessary folders and files.
+
+Local libmctp changes should be kept to a minimum, and submitted
+upstream if possible.
diff --git a/libmctp/alloc.c b/libmctp/alloc.c
new file mode 100644
index 00000000..b6b6761a
--- /dev/null
+++ b/libmctp/alloc.c
@@ -0,0 +1,60 @@ 
+/* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */
+
+#include <assert.h>
+
+#include "libmctp.h"
+#include "libmctp-alloc.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+struct {
+	void	*(*m_alloc)(size_t);
+	void	(*m_free)(void *);
+	void	*(*m_realloc)(void *, size_t);
+} alloc_ops = {
+#ifdef MCTP_DEFAULT_ALLOC
+	malloc,
+	free,
+	realloc,
+#endif
+};
+
+/* internal-only allocation functions */
+void *__mctp_alloc(size_t size)
+{
+	if (alloc_ops.m_alloc)
+		return alloc_ops.m_alloc(size);
+	if (alloc_ops.m_realloc)
+		return alloc_ops.m_realloc(NULL, size);
+	assert(0);
+	return NULL;
+}
+
+void __mctp_free(void *ptr)
+{
+	if (alloc_ops.m_free)
+		alloc_ops.m_free(ptr);
+	else if (alloc_ops.m_realloc)
+		alloc_ops.m_realloc(ptr, 0);
+	else
+		assert(0);
+}
+
+void *__mctp_realloc(void *ptr, size_t size)
+{
+	if (alloc_ops.m_realloc)
+		return alloc_ops.m_realloc(ptr, size);
+	assert(0);
+	return NULL;
+}
+
+void mctp_set_alloc_ops(void *(*m_alloc)(size_t),
+		void (*m_free)(void *),
+		void *(m_realloc)(void *, size_t))
+{
+	alloc_ops.m_alloc = m_alloc;
+	alloc_ops.m_free = m_free;
+	alloc_ops.m_realloc = m_realloc;
+}
diff --git a/libmctp/astlpc.c b/libmctp/astlpc.c
new file mode 100644
index 00000000..407f5efc
--- /dev/null
+++ b/libmctp/astlpc.c
@@ -0,0 +1,1366 @@ 
+/* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#if HAVE_ENDIAN_H
+#include <endian.h>
+#endif
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define pr_fmt(x) "astlpc: " x
+
+#include "container_of.h"
+#include "crc32.h"
+#include "libmctp.h"
+#include "libmctp-alloc.h"
+#include "libmctp-log.h"
+#include "libmctp-astlpc.h"
+#include "range.h"
+
+#ifdef MCTP_HAVE_FILEIO
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <linux/aspeed-lpc-ctrl.h>
+
+/* kernel interface */
+static const char *kcs_path = "/dev/mctp0";
+static const char *lpc_path = "/dev/aspeed-lpc-ctrl";
+
+#endif
+
+struct mctp_astlpc_buffer {
+	uint32_t offset;
+	uint32_t size;
+};
+
+struct mctp_astlpc_layout {
+	struct mctp_astlpc_buffer rx;
+	struct mctp_astlpc_buffer tx;
+};
+
+struct mctp_astlpc_protocol {
+	uint16_t version;
+	uint32_t (*packet_size)(uint32_t body);
+	uint32_t (*body_size)(uint32_t packet);
+	void (*pktbuf_protect)(struct mctp_pktbuf *pkt);
+	bool (*pktbuf_validate)(struct mctp_pktbuf *pkt);
+};
+
+struct mctp_binding_astlpc {
+	struct mctp_binding	binding;
+
+	void *lpc_map;
+	struct mctp_astlpc_layout layout;
+
+	uint8_t mode;
+	uint32_t requested_mtu;
+
+	const struct mctp_astlpc_protocol *proto;
+
+	/* direct ops data */
+	struct mctp_binding_astlpc_ops ops;
+	void *ops_data;
+
+	/* fileio ops data */
+	int kcs_fd;
+	uint8_t kcs_status;
+
+	bool			running;
+};
+
+#define binding_to_astlpc(b) \
+	container_of(b, struct mctp_binding_astlpc, binding)
+
+#define astlpc_prlog(ctx, lvl, fmt, ...)                                       \
+	do {                                                                   \
+		bool __bmc = ((ctx)->mode == MCTP_BINDING_ASTLPC_MODE_BMC);    \
+		mctp_prlog(lvl, pr_fmt("%s: " fmt), __bmc ? "bmc" : "host",    \
+			   ##__VA_ARGS__);                                     \
+	} while (0)
+
+#define astlpc_prerr(ctx, fmt, ...)                                            \
+	astlpc_prlog(ctx, MCTP_LOG_ERR, fmt, ##__VA_ARGS__)
+#define astlpc_prwarn(ctx, fmt, ...)                                           \
+	astlpc_prlog(ctx, MCTP_LOG_WARNING, fmt, ##__VA_ARGS__)
+#define astlpc_prinfo(ctx, fmt, ...)                                           \
+	astlpc_prlog(ctx, MCTP_LOG_INFO, fmt, ##__VA_ARGS__)
+#define astlpc_prdebug(ctx, fmt, ...)                                          \
+	astlpc_prlog(ctx, MCTP_LOG_DEBUG, fmt, ##__VA_ARGS__)
+
+/* clang-format off */
+#define ASTLPC_MCTP_MAGIC	0x4d435450
+#define ASTLPC_VER_BAD	0
+#define ASTLPC_VER_MIN	1
+
+/* Support testing of new binding protocols */
+#ifndef ASTLPC_VER_CUR
+#define ASTLPC_VER_CUR	3
+#endif
+/* clang-format on */
+
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
+#endif
+
+static uint32_t astlpc_packet_size_v1(uint32_t body)
+{
+	assert((body + 4) > body);
+
+	return body + 4;
+}
+
+static uint32_t astlpc_body_size_v1(uint32_t packet)
+{
+	assert((packet - 4) < packet);
+
+	return packet - 4;
+}
+
+void astlpc_pktbuf_protect_v1(struct mctp_pktbuf *pkt)
+{
+	(void)pkt;
+}
+
+bool astlpc_pktbuf_validate_v1(struct mctp_pktbuf *pkt)
+{
+	(void)pkt;
+	return true;
+}
+
+static uint32_t astlpc_packet_size_v3(uint32_t body)
+{
+	assert((body + 4 + 4) > body);
+
+	return body + 4 + 4;
+}
+
+static uint32_t astlpc_body_size_v3(uint32_t packet)
+{
+	assert((packet - 4 - 4) < packet);
+
+	return packet - 4 - 4;
+}
+
+void astlpc_pktbuf_protect_v3(struct mctp_pktbuf *pkt)
+{
+	uint32_t code;
+
+	code = htobe32(crc32(mctp_pktbuf_hdr(pkt), mctp_pktbuf_size(pkt)));
+	mctp_prdebug("%s: 0x%" PRIx32, __func__, code);
+	mctp_pktbuf_push(pkt, &code, 4);
+}
+
+bool astlpc_pktbuf_validate_v3(struct mctp_pktbuf *pkt)
+{
+	uint32_t code;
+	void *check;
+
+	code = be32toh(crc32(mctp_pktbuf_hdr(pkt), mctp_pktbuf_size(pkt) - 4));
+	mctp_prdebug("%s: 0x%" PRIx32, __func__, code);
+	check = mctp_pktbuf_pop(pkt, 4);
+	return check && !memcmp(&code, check, 4);
+}
+
+static const struct mctp_astlpc_protocol astlpc_protocol_version[] = {
+	[0] = {
+		.version = 0,
+		.packet_size = NULL,
+		.body_size = NULL,
+		.pktbuf_protect = NULL,
+		.pktbuf_validate = NULL,
+	},
+	[1] = {
+		.version = 1,
+		.packet_size = astlpc_packet_size_v1,
+		.body_size = astlpc_body_size_v1,
+		.pktbuf_protect = astlpc_pktbuf_protect_v1,
+		.pktbuf_validate = astlpc_pktbuf_validate_v1,
+	},
+	[2] = {
+		.version = 2,
+		.packet_size = astlpc_packet_size_v1,
+		.body_size = astlpc_body_size_v1,
+		.pktbuf_protect = astlpc_pktbuf_protect_v1,
+		.pktbuf_validate = astlpc_pktbuf_validate_v1,
+	},
+	[3] = {
+		.version = 3,
+		.packet_size = astlpc_packet_size_v3,
+		.body_size = astlpc_body_size_v3,
+		.pktbuf_protect = astlpc_pktbuf_protect_v3,
+		.pktbuf_validate = astlpc_pktbuf_validate_v3,
+	},
+};
+
+struct mctp_lpcmap_hdr {
+	uint32_t magic;
+
+	uint16_t bmc_ver_min;
+	uint16_t bmc_ver_cur;
+	uint16_t host_ver_min;
+	uint16_t host_ver_cur;
+	uint16_t negotiated_ver;
+	uint16_t pad0;
+
+	struct {
+		uint32_t rx_offset;
+		uint32_t rx_size;
+		uint32_t tx_offset;
+		uint32_t tx_size;
+	} layout;
+} __attribute__((packed));
+
+static const uint32_t control_size = 0x100;
+
+#define LPC_WIN_SIZE                (1 * 1024 * 1024)
+
+#define KCS_STATUS_BMC_READY		0x80
+#define KCS_STATUS_CHANNEL_ACTIVE	0x40
+#define KCS_STATUS_IBF			0x02
+#define KCS_STATUS_OBF			0x01
+
+static inline int mctp_astlpc_kcs_write(struct mctp_binding_astlpc *astlpc,
+					enum mctp_binding_astlpc_kcs_reg reg,
+					uint8_t val)
+{
+	return astlpc->ops.kcs_write(astlpc->ops_data, reg, val);
+}
+
+static inline int mctp_astlpc_kcs_read(struct mctp_binding_astlpc *astlpc,
+				       enum mctp_binding_astlpc_kcs_reg reg,
+				       uint8_t *val)
+{
+	return astlpc->ops.kcs_read(astlpc->ops_data, reg, val);
+}
+
+static inline int mctp_astlpc_lpc_write(struct mctp_binding_astlpc *astlpc,
+					const void *buf, long offset,
+					size_t len)
+{
+	astlpc_prdebug(astlpc, "%s: %zu bytes to 0x%lx", __func__, len, offset);
+
+	assert(offset >= 0);
+
+	/* Indirect access */
+	if (astlpc->ops.lpc_write) {
+		void *data = astlpc->ops_data;
+
+		return astlpc->ops.lpc_write(data, buf, offset, len);
+	}
+
+	/* Direct mapping */
+	assert(astlpc->lpc_map);
+	memcpy(&((char *)astlpc->lpc_map)[offset], buf, len);
+
+	return 0;
+}
+
+static inline int mctp_astlpc_lpc_read(struct mctp_binding_astlpc *astlpc,
+				       void *buf, long offset, size_t len)
+{
+	astlpc_prdebug(astlpc, "%s: %zu bytes from 0x%lx", __func__, len,
+		       offset);
+
+	assert(offset >= 0);
+
+	/* Indirect access */
+	if (astlpc->ops.lpc_read) {
+		void *data = astlpc->ops_data;
+
+		return astlpc->ops.lpc_read(data, buf, offset, len);
+	}
+
+	/* Direct mapping */
+	assert(astlpc->lpc_map);
+	memcpy(buf, &((char *)astlpc->lpc_map)[offset], len);
+
+	return 0;
+}
+
+static int mctp_astlpc_kcs_set_status(struct mctp_binding_astlpc *astlpc,
+				      uint8_t status)
+{
+	uint8_t data;
+	int rc;
+
+	/* Since we're setting the status register, we want the other endpoint
+	 * to be interrupted. However, some hardware may only raise a host-side
+	 * interrupt on an ODR event.
+	 * So, write a dummy value of 0xff to ODR, which will ensure that an
+	 * interrupt is triggered, and can be ignored by the host.
+	 */
+	data = 0xff;
+
+	rc = mctp_astlpc_kcs_write(astlpc, MCTP_ASTLPC_KCS_REG_STATUS, status);
+	if (rc) {
+		astlpc_prwarn(astlpc, "KCS status write failed");
+		return -1;
+	}
+
+	rc = mctp_astlpc_kcs_write(astlpc, MCTP_ASTLPC_KCS_REG_DATA, data);
+	if (rc) {
+		astlpc_prwarn(astlpc, "KCS dummy data write failed");
+		return -1;
+	}
+
+	return 0;
+}
+
+static int mctp_astlpc_layout_read(struct mctp_binding_astlpc *astlpc,
+				   struct mctp_astlpc_layout *layout)
+{
+	struct mctp_lpcmap_hdr hdr;
+	int rc;
+
+	rc = mctp_astlpc_lpc_read(astlpc, &hdr, 0, sizeof(hdr));
+	if (rc < 0)
+		return rc;
+
+	/* Flip the buffers as the names are defined in terms of the host */
+	if (astlpc->mode == MCTP_BINDING_ASTLPC_MODE_BMC) {
+		layout->rx.offset = be32toh(hdr.layout.tx_offset);
+		layout->rx.size = be32toh(hdr.layout.tx_size);
+		layout->tx.offset = be32toh(hdr.layout.rx_offset);
+		layout->tx.size = be32toh(hdr.layout.rx_size);
+	} else {
+		assert(astlpc->mode == MCTP_BINDING_ASTLPC_MODE_HOST);
+
+		layout->rx.offset = be32toh(hdr.layout.rx_offset);
+		layout->rx.size = be32toh(hdr.layout.rx_size);
+		layout->tx.offset = be32toh(hdr.layout.tx_offset);
+		layout->tx.size = be32toh(hdr.layout.tx_size);
+	}
+
+	return 0;
+}
+
+static int mctp_astlpc_layout_write(struct mctp_binding_astlpc *astlpc,
+				    struct mctp_astlpc_layout *layout)
+{
+	uint32_t rx_size_be;
+
+	if (astlpc->mode == MCTP_BINDING_ASTLPC_MODE_BMC) {
+		struct mctp_lpcmap_hdr hdr;
+
+		/*
+		 * Flip the buffers as the names are defined in terms of the
+		 * host
+		 */
+		hdr.layout.rx_offset = htobe32(layout->tx.offset);
+		hdr.layout.rx_size = htobe32(layout->tx.size);
+		hdr.layout.tx_offset = htobe32(layout->rx.offset);
+		hdr.layout.tx_size = htobe32(layout->rx.size);
+
+		return mctp_astlpc_lpc_write(astlpc, &hdr.layout,
+				offsetof(struct mctp_lpcmap_hdr, layout),
+				sizeof(hdr.layout));
+	}
+
+	assert(astlpc->mode == MCTP_BINDING_ASTLPC_MODE_HOST);
+
+	/*
+	 * As of v2 we only need to write rx_size - the offsets are controlled
+	 * by the BMC, as is the BMC's rx_size (host tx_size).
+	 */
+	rx_size_be = htobe32(layout->rx.size);
+	return mctp_astlpc_lpc_write(astlpc, &rx_size_be,
+			offsetof(struct mctp_lpcmap_hdr, layout.rx_size),
+			sizeof(rx_size_be));
+}
+
+static bool
+mctp_astlpc_buffer_validate(const struct mctp_binding_astlpc *astlpc,
+			    const struct mctp_astlpc_buffer *buf,
+			    const char *name)
+{
+	/* Check for overflow */
+	if (buf->offset + buf->size < buf->offset) {
+		mctp_prerr(
+			"%s packet buffer parameters overflow: offset: 0x%" PRIx32
+			", size: %" PRIu32,
+			name, buf->offset, buf->size);
+		return false;
+	}
+
+	/* Check that the buffers are contained within the allocated space */
+	if (buf->offset + buf->size > LPC_WIN_SIZE) {
+		mctp_prerr(
+			"%s packet buffer parameters exceed %uM window size: offset: 0x%" PRIx32
+			", size: %" PRIu32,
+			name, (LPC_WIN_SIZE / (1024 * 1024)), buf->offset,
+			buf->size);
+		return false;
+	}
+
+	/* Check that the baseline transmission unit is supported */
+	if (buf->size < astlpc->proto->packet_size(MCTP_PACKET_SIZE(MCTP_BTU))) {
+		mctp_prerr(
+			"%s packet buffer too small: Require %" PRIu32 " bytes to support the %u byte baseline transmission unit, found %" PRIu32,
+			name,
+			astlpc->proto->packet_size(MCTP_PACKET_SIZE(MCTP_BTU)),
+			MCTP_BTU, buf->size);
+		return false;
+	}
+
+	/* Check for overlap with the control space */
+	if (buf->offset < control_size) {
+		mctp_prerr(
+			"%s packet buffer overlaps control region {0x%" PRIx32
+			", %" PRIu32 "}: Rx {0x%" PRIx32 ", %" PRIu32 "}",
+			name, 0U, control_size, buf->offset, buf->size);
+		return false;
+	}
+
+	return true;
+}
+
+static bool
+mctp_astlpc_layout_validate(const struct mctp_binding_astlpc *astlpc,
+			    const struct mctp_astlpc_layout *layout)
+{
+	const struct mctp_astlpc_buffer *rx = &layout->rx;
+	const struct mctp_astlpc_buffer *tx = &layout->tx;
+	bool rx_valid, tx_valid;
+
+	rx_valid = mctp_astlpc_buffer_validate(astlpc, rx, "Rx");
+	tx_valid = mctp_astlpc_buffer_validate(astlpc, tx, "Tx");
+
+	if (!(rx_valid && tx_valid))
+		return false;
+
+	/* Check that the buffers are disjoint */
+	if ((rx->offset <= tx->offset && rx->offset + rx->size > tx->offset) ||
+	    (tx->offset <= rx->offset && tx->offset + tx->size > rx->offset)) {
+		mctp_prerr("Rx and Tx packet buffers overlap: Rx {0x%" PRIx32
+			   ", %" PRIu32 "}, Tx {0x%" PRIx32 ", %" PRIu32 "}",
+			   rx->offset, rx->size, tx->offset, tx->size);
+		return false;
+	}
+
+	return true;
+}
+
+static int mctp_astlpc_init_bmc(struct mctp_binding_astlpc *astlpc)
+{
+	struct mctp_lpcmap_hdr hdr = { 0 };
+	uint8_t status;
+	uint32_t sz;
+
+	/*
+	 * The largest buffer size is half of the allocated MCTP space
+	 * excluding the control space.
+	 */
+	sz = ((LPC_WIN_SIZE - control_size) / 2);
+
+	/*
+	 * Trim the MTU to a multiple of 16 to meet the requirements of 12.17
+	 * Query Hop in DSP0236 v1.3.0.
+	 */
+	sz = MCTP_BODY_SIZE(astlpc->proto->body_size(sz));
+	sz &= ~0xfUL;
+	sz = astlpc->proto->packet_size(MCTP_PACKET_SIZE(sz));
+
+	if (astlpc->requested_mtu) {
+		uint32_t rpkt, rmtu;
+
+		rmtu = astlpc->requested_mtu;
+		rpkt = astlpc->proto->packet_size(MCTP_PACKET_SIZE(rmtu));
+		sz = MIN(sz, rpkt);
+	}
+
+	/* Flip the buffers as the names are defined in terms of the host */
+	astlpc->layout.tx.offset = control_size;
+	astlpc->layout.tx.size = sz;
+	astlpc->layout.rx.offset =
+		astlpc->layout.tx.offset + astlpc->layout.tx.size;
+	astlpc->layout.rx.size = sz;
+
+	if (!mctp_astlpc_layout_validate(astlpc, &astlpc->layout)) {
+		astlpc_prerr(astlpc, "Cannot support an MTU of %" PRIu32, sz);
+		return -EINVAL;
+	}
+
+	hdr = (struct mctp_lpcmap_hdr){
+		.magic = htobe32(ASTLPC_MCTP_MAGIC),
+		.bmc_ver_min = htobe16(ASTLPC_VER_MIN),
+		.bmc_ver_cur = htobe16(ASTLPC_VER_CUR),
+
+		/* Flip the buffers back as we're now describing the host's
+		 * configuration to the host */
+		.layout.rx_offset = htobe32(astlpc->layout.tx.offset),
+		.layout.rx_size = htobe32(astlpc->layout.tx.size),
+		.layout.tx_offset = htobe32(astlpc->layout.rx.offset),
+		.layout.tx_size = htobe32(astlpc->layout.rx.size),
+	};
+
+	mctp_astlpc_lpc_write(astlpc, &hdr, 0, sizeof(hdr));
+
+	/*
+	 * Set status indicating that the BMC is now active. Be explicit about
+	 * clearing OBF; we're reinitialising the binding and so any previous
+	 * buffer state is irrelevant.
+	 */
+	status = KCS_STATUS_BMC_READY & ~KCS_STATUS_OBF;
+	return mctp_astlpc_kcs_set_status(astlpc, status);
+}
+
+static int mctp_binding_astlpc_start_bmc(struct mctp_binding *b)
+{
+	struct mctp_binding_astlpc *astlpc =
+		container_of(b, struct mctp_binding_astlpc, binding);
+
+	astlpc->proto = &astlpc_protocol_version[ASTLPC_VER_CUR];
+
+	return mctp_astlpc_init_bmc(astlpc);
+}
+
+static bool mctp_astlpc_validate_version(uint16_t bmc_ver_min,
+					 uint16_t bmc_ver_cur,
+					 uint16_t host_ver_min,
+					 uint16_t host_ver_cur)
+{
+	if (!(bmc_ver_min && bmc_ver_cur && host_ver_min && host_ver_cur)) {
+		mctp_prerr("Invalid version present in [%" PRIu16 ", %" PRIu16
+			   "], [%" PRIu16 ", %" PRIu16 "]",
+			   bmc_ver_min, bmc_ver_cur, host_ver_min,
+			   host_ver_cur);
+		return false;
+	} else if (bmc_ver_min > bmc_ver_cur) {
+		mctp_prerr("Invalid bmc version range [%" PRIu16 ", %" PRIu16
+			   "]",
+			   bmc_ver_min, bmc_ver_cur);
+		return false;
+	} else if (host_ver_min > host_ver_cur) {
+		mctp_prerr("Invalid host version range [%" PRIu16 ", %" PRIu16
+			   "]",
+			   host_ver_min, host_ver_cur);
+		return false;
+	} else if ((host_ver_cur < bmc_ver_min) ||
+		   (host_ver_min > bmc_ver_cur)) {
+		mctp_prerr(
+			"Unable to satisfy version negotiation with ranges [%" PRIu16
+			", %" PRIu16 "] and [%" PRIu16 ", %" PRIu16 "]",
+			bmc_ver_min, bmc_ver_cur, host_ver_min, host_ver_cur);
+		return false;
+	}
+
+	return true;
+}
+
+static int mctp_astlpc_negotiate_layout_host(struct mctp_binding_astlpc *astlpc)
+{
+	struct mctp_astlpc_layout layout;
+	uint32_t rmtu;
+	uint32_t sz;
+	int rc;
+
+	rc = mctp_astlpc_layout_read(astlpc, &layout);
+	if (rc < 0)
+		return rc;
+
+	if (!mctp_astlpc_layout_validate(astlpc, &layout)) {
+		astlpc_prerr(
+			astlpc,
+			"BMC provided invalid buffer layout: Rx {0x%" PRIx32
+			", %" PRIu32 "}, Tx {0x%" PRIx32 ", %" PRIu32 "}",
+			layout.rx.offset, layout.rx.size, layout.tx.offset,
+			layout.tx.size);
+		return -EINVAL;
+	}
+
+	astlpc_prinfo(astlpc, "Desire an MTU of %" PRIu32 " bytes",
+		      astlpc->requested_mtu);
+
+	rmtu = astlpc->requested_mtu;
+	sz = astlpc->proto->packet_size(MCTP_PACKET_SIZE(rmtu));
+	layout.rx.size = sz;
+
+	if (!mctp_astlpc_layout_validate(astlpc, &layout)) {
+		astlpc_prerr(
+			astlpc,
+			"Generated invalid buffer layout with size %" PRIu32
+			": Rx {0x%" PRIx32 ", %" PRIu32 "}, Tx {0x%" PRIx32
+			", %" PRIu32 "}",
+			sz, layout.rx.offset, layout.rx.size, layout.tx.offset,
+			layout.tx.size);
+		return -EINVAL;
+	}
+
+	astlpc_prinfo(astlpc, "Requesting MTU of %" PRIu32 " bytes",
+		      astlpc->requested_mtu);
+
+	return mctp_astlpc_layout_write(astlpc, &layout);
+}
+
+static uint16_t mctp_astlpc_negotiate_version(uint16_t bmc_ver_min,
+					      uint16_t bmc_ver_cur,
+					      uint16_t host_ver_min,
+					      uint16_t host_ver_cur)
+{
+	if (!mctp_astlpc_validate_version(bmc_ver_min, bmc_ver_cur,
+					  host_ver_min, host_ver_cur))
+		return ASTLPC_VER_BAD;
+
+	if (bmc_ver_cur < host_ver_cur)
+		return bmc_ver_cur;
+
+	return host_ver_cur;
+}
+
+static int mctp_astlpc_init_host(struct mctp_binding_astlpc *astlpc)
+{
+	const uint16_t ver_min_be = htobe16(ASTLPC_VER_MIN);
+	const uint16_t ver_cur_be = htobe16(ASTLPC_VER_CUR);
+	uint16_t bmc_ver_min, bmc_ver_cur, negotiated;
+	struct mctp_lpcmap_hdr hdr;
+	uint8_t status;
+	int rc;
+
+	rc = mctp_astlpc_kcs_read(astlpc, MCTP_ASTLPC_KCS_REG_STATUS, &status);
+	if (rc) {
+		mctp_prwarn("KCS status read failed");
+		return rc;
+	}
+
+	astlpc->kcs_status = status;
+
+	if (!(status & KCS_STATUS_BMC_READY))
+		return -EHOSTDOWN;
+
+	mctp_astlpc_lpc_read(astlpc, &hdr, 0, sizeof(hdr));
+
+	bmc_ver_min = be16toh(hdr.bmc_ver_min);
+	bmc_ver_cur = be16toh(hdr.bmc_ver_cur);
+
+	/* Calculate the expected value of negotiated_ver */
+	negotiated = mctp_astlpc_negotiate_version(bmc_ver_min, bmc_ver_cur,
+						   ASTLPC_VER_MIN,
+						   ASTLPC_VER_CUR);
+	if (!negotiated) {
+		astlpc_prerr(astlpc, "Cannot negotiate with invalid versions");
+		return -EINVAL;
+	}
+
+	/* Assign protocol ops so we can calculate the packet buffer sizes */
+	assert(negotiated < ARRAY_SIZE(astlpc_protocol_version));
+	astlpc->proto = &astlpc_protocol_version[negotiated];
+
+	/* Negotiate packet buffers in v2 style if the BMC supports it */
+	if (negotiated >= 2) {
+		rc = mctp_astlpc_negotiate_layout_host(astlpc);
+		if (rc < 0)
+			return rc;
+	}
+
+	/* Advertise the host's supported protocol versions */
+	mctp_astlpc_lpc_write(astlpc, &ver_min_be,
+			      offsetof(struct mctp_lpcmap_hdr, host_ver_min),
+			      sizeof(ver_min_be));
+
+	mctp_astlpc_lpc_write(astlpc, &ver_cur_be,
+			      offsetof(struct mctp_lpcmap_hdr, host_ver_cur),
+			      sizeof(ver_cur_be));
+
+	/* Send channel init command */
+	rc = mctp_astlpc_kcs_write(astlpc, MCTP_ASTLPC_KCS_REG_DATA, 0x0);
+	if (rc) {
+		astlpc_prwarn(astlpc, "KCS write failed");
+	}
+
+	/*
+	 * Configure the host so `astlpc->proto->version == 0` holds until we
+	 * receive a subsequent status update from the BMC. Until then,
+	 * `astlpc->proto->version == 0` indicates that we're yet to complete
+	 * the channel initialisation handshake.
+	 *
+	 * When the BMC provides a status update with KCS_STATUS_CHANNEL_ACTIVE
+	 * set we will assign the appropriate protocol ops struct in accordance
+	 * with `negotiated_ver`.
+	 */
+	astlpc->proto = &astlpc_protocol_version[ASTLPC_VER_BAD];
+
+	return rc;
+}
+
+static int mctp_binding_astlpc_start_host(struct mctp_binding *b)
+{
+	struct mctp_binding_astlpc *astlpc =
+		container_of(b, struct mctp_binding_astlpc, binding);
+
+	return mctp_astlpc_init_host(astlpc);
+}
+
+static bool __mctp_astlpc_kcs_ready(struct mctp_binding_astlpc *astlpc,
+				    uint8_t status, bool is_write)
+{
+	bool is_bmc;
+	bool ready_state;
+	uint8_t flag;
+
+	is_bmc = (astlpc->mode == MCTP_BINDING_ASTLPC_MODE_BMC);
+	flag = (is_bmc ^ is_write) ? KCS_STATUS_IBF : KCS_STATUS_OBF;
+	ready_state = is_write ? 0 : 1;
+
+	return !!(status & flag) == ready_state;
+}
+
+static inline bool
+mctp_astlpc_kcs_read_ready(struct mctp_binding_astlpc *astlpc, uint8_t status)
+{
+	return __mctp_astlpc_kcs_ready(astlpc, status, false);
+}
+
+static inline bool
+mctp_astlpc_kcs_write_ready(struct mctp_binding_astlpc *astlpc, uint8_t status)
+{
+	return __mctp_astlpc_kcs_ready(astlpc, status, true);
+}
+
+static int mctp_astlpc_kcs_send(struct mctp_binding_astlpc *astlpc,
+		uint8_t data)
+{
+	uint8_t status;
+	int rc;
+
+	for (;;) {
+		rc = mctp_astlpc_kcs_read(astlpc, MCTP_ASTLPC_KCS_REG_STATUS,
+					  &status);
+		if (rc) {
+			astlpc_prwarn(astlpc, "KCS status read failed");
+			return -1;
+		}
+		if (mctp_astlpc_kcs_write_ready(astlpc, status))
+			break;
+		/* todo: timeout */
+	}
+
+	rc = mctp_astlpc_kcs_write(astlpc, MCTP_ASTLPC_KCS_REG_DATA, data);
+	if (rc) {
+		astlpc_prwarn(astlpc, "KCS data write failed");
+		return -1;
+	}
+
+	return 0;
+}
+
+static int mctp_binding_astlpc_tx(struct mctp_binding *b,
+		struct mctp_pktbuf *pkt)
+{
+	struct mctp_binding_astlpc *astlpc = binding_to_astlpc(b);
+	uint32_t len, len_be;
+	struct mctp_hdr *hdr;
+
+	hdr = mctp_pktbuf_hdr(pkt);
+	len = mctp_pktbuf_size(pkt);
+
+	astlpc_prdebug(astlpc,
+		       "%s: Transmitting %" PRIu32
+		       "-byte packet (%hhu, %hhu, 0x%hhx)",
+		       __func__, len, hdr->src, hdr->dest, hdr->flags_seq_tag);
+
+	if (len > astlpc->proto->body_size(astlpc->layout.tx.size)) {
+		astlpc_prwarn(astlpc, "invalid TX len %" PRIu32 ": %" PRIu32, len,
+				astlpc->proto->body_size(astlpc->layout.tx.size));
+		return -1;
+	}
+
+	len_be = htobe32(len);
+	mctp_astlpc_lpc_write(astlpc, &len_be, astlpc->layout.tx.offset,
+			      sizeof(len_be));
+
+	astlpc->proto->pktbuf_protect(pkt);
+	len = mctp_pktbuf_size(pkt);
+
+	mctp_astlpc_lpc_write(astlpc, hdr, astlpc->layout.tx.offset + 4, len);
+
+	mctp_binding_set_tx_enabled(b, false);
+
+	mctp_astlpc_kcs_send(astlpc, 0x1);
+
+	return 0;
+}
+
+static uint32_t mctp_astlpc_calculate_mtu(struct mctp_binding_astlpc *astlpc,
+					  struct mctp_astlpc_layout *layout)
+{
+	uint32_t low, high, limit, rpkt;
+
+	/* Derive the largest MTU the BMC _can_ support */
+	low = MIN(astlpc->layout.rx.offset, astlpc->layout.tx.offset);
+	high = MAX(astlpc->layout.rx.offset, astlpc->layout.tx.offset);
+	limit = high - low;
+
+	/* Determine the largest MTU the BMC _wants_ to support */
+	if (astlpc->requested_mtu) {
+		uint32_t rmtu = astlpc->requested_mtu;
+
+		rpkt = astlpc->proto->packet_size(MCTP_PACKET_SIZE(rmtu));
+		limit = MIN(limit, rpkt);
+	}
+
+	/* Determine the accepted MTU, applied both directions by convention */
+	rpkt = MIN(limit, layout->tx.size);
+	return MCTP_BODY_SIZE(astlpc->proto->body_size(rpkt));
+}
+
+static int mctp_astlpc_negotiate_layout_bmc(struct mctp_binding_astlpc *astlpc)
+{
+	struct mctp_astlpc_layout proposed, pending;
+	uint32_t sz, mtu;
+	int rc;
+
+	/* Do we have a valid protocol version? */
+	if (!astlpc->proto->version)
+		return -EINVAL;
+
+	/* Extract the host's proposed layout */
+	rc = mctp_astlpc_layout_read(astlpc, &proposed);
+	if (rc < 0)
+		return rc;
+
+	/* Do we have a reasonable layout? */
+	if (!mctp_astlpc_layout_validate(astlpc, &proposed))
+		return -EINVAL;
+
+	/* Negotiate the MTU */
+	mtu = mctp_astlpc_calculate_mtu(astlpc, &proposed);
+	sz = astlpc->proto->packet_size(MCTP_PACKET_SIZE(mtu));
+
+	/*
+	 * Use symmetric MTUs by convention and to pass constraints in rx/tx
+	 * functions
+	 */
+	pending = astlpc->layout;
+	pending.tx.size = sz;
+	pending.rx.size = sz;
+
+	if (mctp_astlpc_layout_validate(astlpc, &pending)) {
+		/* We found a sensible Rx MTU, so honour it */
+		astlpc->layout = pending;
+
+		/* Enforce the negotiated MTU */
+		rc = mctp_astlpc_layout_write(astlpc, &astlpc->layout);
+		if (rc < 0)
+			return rc;
+
+		astlpc_prinfo(astlpc, "Negotiated an MTU of %" PRIu32 " bytes",
+			      mtu);
+	} else {
+		astlpc_prwarn(astlpc, "MTU negotiation failed");
+		return -EINVAL;
+	}
+
+	if (astlpc->proto->version >= 2)
+		astlpc->binding.pkt_size = MCTP_PACKET_SIZE(mtu);
+
+	return 0;
+}
+
+static void mctp_astlpc_init_channel(struct mctp_binding_astlpc *astlpc)
+{
+	uint16_t negotiated, negotiated_be;
+	struct mctp_lpcmap_hdr hdr;
+	uint8_t status;
+	int rc;
+
+	mctp_astlpc_lpc_read(astlpc, &hdr, 0, sizeof(hdr));
+
+	/* Version negotiation */
+	negotiated =
+		mctp_astlpc_negotiate_version(ASTLPC_VER_MIN, ASTLPC_VER_CUR,
+					      be16toh(hdr.host_ver_min),
+					      be16toh(hdr.host_ver_cur));
+
+	/* MTU negotiation requires knowing which protocol we'll use */
+	assert(negotiated < ARRAY_SIZE(astlpc_protocol_version));
+	astlpc->proto = &astlpc_protocol_version[negotiated];
+
+	/* Host Rx MTU negotiation: Failure terminates channel init */
+	rc = mctp_astlpc_negotiate_layout_bmc(astlpc);
+	if (rc < 0)
+		negotiated = ASTLPC_VER_BAD;
+
+	/* Populate the negotiated version */
+	negotiated_be = htobe16(negotiated);
+	mctp_astlpc_lpc_write(astlpc, &negotiated_be,
+			      offsetof(struct mctp_lpcmap_hdr, negotiated_ver),
+			      sizeof(negotiated_be));
+
+	/* Finalise the configuration */
+	status = KCS_STATUS_BMC_READY | KCS_STATUS_OBF;
+	if (negotiated > 0) {
+		astlpc_prinfo(astlpc, "Negotiated binding version %" PRIu16,
+			      negotiated);
+		status |= KCS_STATUS_CHANNEL_ACTIVE;
+	} else {
+		astlpc_prerr(astlpc, "Failed to initialise channel");
+	}
+
+	mctp_astlpc_kcs_set_status(astlpc, status);
+
+	mctp_binding_set_tx_enabled(&astlpc->binding,
+				    status & KCS_STATUS_CHANNEL_ACTIVE);
+}
+
+static void mctp_astlpc_rx_start(struct mctp_binding_astlpc *astlpc)
+{
+	struct mctp_pktbuf *pkt;
+	uint32_t body, packet;
+
+	mctp_astlpc_lpc_read(astlpc, &body, astlpc->layout.rx.offset,
+			     sizeof(body));
+	body = be32toh(body);
+
+	if (body > astlpc->proto->body_size(astlpc->layout.rx.size)) {
+		astlpc_prwarn(astlpc, "invalid RX len 0x%x", body);
+		return;
+	}
+
+	assert(astlpc->binding.pkt_size >= 0);
+	if (body > (uint32_t)astlpc->binding.pkt_size) {
+		astlpc_prwarn(astlpc, "invalid RX len 0x%x", body);
+		return;
+	}
+
+	/* Eliminate the medium-specific header that we just read */
+	packet = astlpc->proto->packet_size(body) - 4;
+	pkt = mctp_pktbuf_alloc(&astlpc->binding, packet);
+	if (!pkt) {
+		astlpc_prwarn(astlpc, "unable to allocate pktbuf len 0x%x", packet);
+		return;
+	}
+
+	/*
+	 * Read payload and medium-specific trailer from immediately after the
+	 * medium-specific header.
+	 */
+	mctp_astlpc_lpc_read(astlpc, mctp_pktbuf_hdr(pkt),
+			     astlpc->layout.rx.offset + 4, packet);
+
+	/* Inform the other side of the MCTP interface that we have read
+	 * the packet off the bus before handling the contents of the packet.
+	 */
+	mctp_astlpc_kcs_send(astlpc, 0x2);
+
+	/*
+	 * v3 will validate the CRC32 in the medium-specific trailer and adjust
+	 * the packet size accordingly. On older protocols validation is a no-op
+	 * that always returns true.
+	 */
+	if (astlpc->proto->pktbuf_validate(pkt)) {
+		mctp_bus_rx(&astlpc->binding, pkt);
+	} else {
+		/* TODO: Drop any associated assembly */
+		mctp_pktbuf_free(pkt);
+		astlpc_prdebug(astlpc, "Dropped corrupt packet");
+	}
+}
+
+static void mctp_astlpc_tx_complete(struct mctp_binding_astlpc *astlpc)
+{
+	mctp_binding_set_tx_enabled(&astlpc->binding, true);
+}
+
+static int mctp_astlpc_finalise_channel(struct mctp_binding_astlpc *astlpc)
+{
+	struct mctp_astlpc_layout layout;
+	uint16_t negotiated;
+	int rc;
+
+	rc = mctp_astlpc_lpc_read(astlpc, &negotiated,
+				  offsetof(struct mctp_lpcmap_hdr,
+					   negotiated_ver),
+				  sizeof(negotiated));
+	if (rc < 0)
+		return rc;
+
+	negotiated = be16toh(negotiated);
+	astlpc_prerr(astlpc, "Version negotiation got: %u", negotiated);
+
+	if (negotiated == ASTLPC_VER_BAD || negotiated < ASTLPC_VER_MIN ||
+	    negotiated > ASTLPC_VER_CUR) {
+		astlpc_prerr(astlpc, "Failed to negotiate version, got: %u\n",
+			     negotiated);
+		return -EINVAL;
+	}
+
+	assert(negotiated < ARRAY_SIZE(astlpc_protocol_version));
+	astlpc->proto = &astlpc_protocol_version[negotiated];
+
+	rc = mctp_astlpc_layout_read(astlpc, &layout);
+	if (rc < 0)
+		return rc;
+
+	if (!mctp_astlpc_layout_validate(astlpc, &layout)) {
+		mctp_prerr("BMC proposed invalid buffer parameters");
+		return -EINVAL;
+	}
+
+	astlpc->layout = layout;
+
+	if (negotiated >= 2)
+		astlpc->binding.pkt_size =
+			astlpc->proto->body_size(astlpc->layout.tx.size);
+
+	return 0;
+}
+
+static int mctp_astlpc_update_channel(struct mctp_binding_astlpc *astlpc,
+				      uint8_t status)
+{
+	uint8_t updated;
+	int rc = 0;
+
+	assert(astlpc->mode == MCTP_BINDING_ASTLPC_MODE_HOST);
+
+	updated = astlpc->kcs_status ^ status;
+
+	astlpc_prdebug(astlpc, "%s: status: 0x%x, update: 0x%x", __func__,
+		       status, updated);
+
+	if (updated & KCS_STATUS_BMC_READY) {
+		if (status & KCS_STATUS_BMC_READY) {
+			astlpc->kcs_status = status;
+			return astlpc->binding.start(&astlpc->binding);
+		} else {
+			mctp_binding_set_tx_enabled(&astlpc->binding, false);
+		}
+	}
+
+	if (astlpc->proto->version == 0 ||
+			updated & KCS_STATUS_CHANNEL_ACTIVE) {
+		bool enable;
+
+		rc = mctp_astlpc_finalise_channel(astlpc);
+		enable = (status & KCS_STATUS_CHANNEL_ACTIVE) && rc == 0;
+
+		mctp_binding_set_tx_enabled(&astlpc->binding, enable);
+	}
+
+	astlpc->kcs_status = status;
+
+	return rc;
+}
+
+int mctp_astlpc_poll(struct mctp_binding_astlpc *astlpc)
+{
+	uint8_t status, data;
+	int rc;
+
+	rc = mctp_astlpc_kcs_read(astlpc, MCTP_ASTLPC_KCS_REG_STATUS, &status);
+	if (rc) {
+		astlpc_prwarn(astlpc, "KCS read error");
+		return -1;
+	}
+
+	astlpc_prdebug(astlpc, "%s: status: 0x%hhx", __func__, status);
+
+	if (!mctp_astlpc_kcs_read_ready(astlpc, status))
+		return 0;
+
+	rc = mctp_astlpc_kcs_read(astlpc, MCTP_ASTLPC_KCS_REG_DATA, &data);
+	if (rc) {
+		astlpc_prwarn(astlpc, "KCS data read error");
+		return -1;
+	}
+
+	astlpc_prdebug(astlpc, "%s: data: 0x%hhx", __func__, data);
+
+	if (!astlpc->proto->version && !(data == 0x0 || data == 0xff)) {
+		astlpc_prwarn(astlpc, "Invalid message for binding state: 0x%x",
+			      data);
+		return 0;
+	}
+
+	switch (data) {
+	case 0x0:
+		mctp_astlpc_init_channel(astlpc);
+		break;
+	case 0x1:
+		mctp_astlpc_rx_start(astlpc);
+		break;
+	case 0x2:
+		mctp_astlpc_tx_complete(astlpc);
+		break;
+	case 0xff:
+		/* No responsibilities for the BMC on 0xff */
+		if (astlpc->mode == MCTP_BINDING_ASTLPC_MODE_HOST) {
+			rc = mctp_astlpc_update_channel(astlpc, status);
+			if (rc < 0)
+				return rc;
+		}
+		break;
+	default:
+		astlpc_prwarn(astlpc, "unknown message 0x%x", data);
+	}
+
+	/* Handle silent loss of bmc-ready */
+	if (astlpc->mode == MCTP_BINDING_ASTLPC_MODE_HOST) {
+		if (!(status & KCS_STATUS_BMC_READY && data == 0xff))
+			return mctp_astlpc_update_channel(astlpc, status);
+	}
+
+	return rc;
+}
+
+/* allocate and basic initialisation */
+static struct mctp_binding_astlpc *__mctp_astlpc_init(uint8_t mode,
+						      uint32_t mtu)
+{
+	struct mctp_binding_astlpc *astlpc;
+
+	assert((mode == MCTP_BINDING_ASTLPC_MODE_BMC) ||
+	       (mode == MCTP_BINDING_ASTLPC_MODE_HOST));
+
+	astlpc = __mctp_alloc(sizeof(*astlpc));
+	if (!astlpc)
+		return NULL;
+
+	memset(astlpc, 0, sizeof(*astlpc));
+	astlpc->mode = mode;
+	astlpc->lpc_map = NULL;
+	astlpc->requested_mtu = mtu;
+	astlpc->binding.name = "astlpc";
+	astlpc->binding.version = 1;
+	astlpc->binding.pkt_size =
+		MCTP_PACKET_SIZE(mtu > MCTP_BTU ? mtu : MCTP_BTU);
+	astlpc->binding.pkt_header = 4;
+	astlpc->binding.pkt_trailer = 4;
+	astlpc->binding.tx = mctp_binding_astlpc_tx;
+	if (mode == MCTP_BINDING_ASTLPC_MODE_BMC)
+		astlpc->binding.start = mctp_binding_astlpc_start_bmc;
+	else if (mode == MCTP_BINDING_ASTLPC_MODE_HOST)
+		astlpc->binding.start = mctp_binding_astlpc_start_host;
+	else {
+		astlpc_prerr(astlpc, "%s: Invalid mode: %d\n", __func__, mode);
+		__mctp_free(astlpc);
+		return NULL;
+	}
+
+	return astlpc;
+}
+
+struct mctp_binding *mctp_binding_astlpc_core(struct mctp_binding_astlpc *b)
+{
+	return &b->binding;
+}
+
+struct mctp_binding_astlpc *
+mctp_astlpc_init(uint8_t mode, uint32_t mtu, void *lpc_map,
+		 const struct mctp_binding_astlpc_ops *ops, void *ops_data)
+{
+	struct mctp_binding_astlpc *astlpc;
+
+	if (!(mode == MCTP_BINDING_ASTLPC_MODE_BMC ||
+	      mode == MCTP_BINDING_ASTLPC_MODE_HOST)) {
+		mctp_prerr("Unknown binding mode: %u", mode);
+		return NULL;
+	}
+
+	astlpc = __mctp_astlpc_init(mode, mtu);
+	if (!astlpc)
+		return NULL;
+
+	memcpy(&astlpc->ops, ops, sizeof(astlpc->ops));
+	astlpc->ops_data = ops_data;
+	astlpc->lpc_map = lpc_map;
+	astlpc->mode = mode;
+
+	return astlpc;
+}
+
+struct mctp_binding_astlpc *
+mctp_astlpc_init_ops(const struct mctp_binding_astlpc_ops *ops, void *ops_data,
+		     void *lpc_map)
+{
+	return mctp_astlpc_init(MCTP_BINDING_ASTLPC_MODE_BMC, MCTP_BTU, lpc_map,
+				ops, ops_data);
+}
+
+void mctp_astlpc_destroy(struct mctp_binding_astlpc *astlpc)
+{
+	/* Clear channel-active and bmc-ready */
+	if (astlpc->mode == MCTP_BINDING_ASTLPC_MODE_BMC)
+		mctp_astlpc_kcs_set_status(astlpc, 0);
+	__mctp_free(astlpc);
+}
+
+#ifdef MCTP_HAVE_FILEIO
+
+static int mctp_astlpc_init_fileio_lpc(struct mctp_binding_astlpc *astlpc)
+{
+	struct aspeed_lpc_ctrl_mapping map = {
+		.window_type = ASPEED_LPC_CTRL_WINDOW_MEMORY,
+		.window_id = 0, /* There's only one */
+		.flags = 0,
+		.addr = 0,
+		.offset = 0,
+		.size = 0
+	};
+	void *lpc_map_base;
+	int fd, rc;
+
+	fd = open(lpc_path, O_RDWR | O_SYNC);
+	if (fd < 0) {
+		astlpc_prwarn(astlpc, "LPC open (%s) failed", lpc_path);
+		return -1;
+	}
+
+	rc = ioctl(fd, ASPEED_LPC_CTRL_IOCTL_GET_SIZE, &map);
+	if (rc) {
+		astlpc_prwarn(astlpc, "LPC GET_SIZE failed");
+		close(fd);
+		return -1;
+	}
+
+	/*
+	 * 🚨🚨🚨
+	 *
+	 * Decouple ourselves from hiomapd[1] (another user of the FW2AHB) by
+	 * mapping the FW2AHB to the reserved memory here as well.
+	 *
+	 * It's not possible to use the MCTP ASTLPC binding on machines that
+	 * need the FW2AHB bridge mapped anywhere except to the reserved memory
+	 * (e.g. the host SPI NOR).
+	 *
+	 * [1] https://github.com/openbmc/hiomapd/
+	 *
+	 * 🚨🚨🚨
+	 *
+	 * The following calculation must align with what's going on in
+	 * hiomapd's lpc.c so as not to disrupt its behaviour:
+	 *
+	 * https://github.com/openbmc/hiomapd/blob/5ff50e3cbd7702aefc185264e4adfb9952040575/lpc.c#L68
+	 *
+	 * 🚨🚨🚨
+	 */
+
+	/* Map the reserved memory at the top of the 28-bit LPC firmware address space */
+	map.addr = 0x0FFFFFFF & -map.size;
+	astlpc_prinfo(astlpc,
+		      "Configuring FW2AHB to map reserved memory at 0x%08x for 0x%x in the LPC FW cycle address-space",
+		      map.addr, map.size);
+
+	rc = ioctl(fd, ASPEED_LPC_CTRL_IOCTL_MAP, &map);
+	if (rc) {
+		astlpc_prwarn(astlpc, "Failed to map FW2AHB to reserved memory");
+		close(fd);
+		return -1;
+	}
+
+	/* Map the reserved memory into our address space */
+	lpc_map_base =
+		mmap(NULL, map.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+	if (lpc_map_base == MAP_FAILED) {
+		astlpc_prwarn(astlpc, "LPC mmap failed");
+		rc = -1;
+	} else {
+		astlpc->lpc_map = lpc_map_base + map.size - LPC_WIN_SIZE;
+	}
+
+	close(fd);
+
+	return rc;
+}
+
+static int mctp_astlpc_init_fileio_kcs(struct mctp_binding_astlpc *astlpc)
+{
+	astlpc->kcs_fd = open(kcs_path, O_RDWR);
+	if (astlpc->kcs_fd < 0)
+		return -1;
+
+	return 0;
+}
+
+static int __mctp_astlpc_fileio_kcs_read(void *arg,
+		enum mctp_binding_astlpc_kcs_reg reg, uint8_t *val)
+{
+	struct mctp_binding_astlpc *astlpc = arg;
+	off_t offset = reg;
+	int rc;
+
+	rc = pread(astlpc->kcs_fd, val, 1, offset);
+
+	return rc == 1 ? 0 : -1;
+}
+
+static int __mctp_astlpc_fileio_kcs_write(void *arg,
+		enum mctp_binding_astlpc_kcs_reg reg, uint8_t val)
+{
+	struct mctp_binding_astlpc *astlpc = arg;
+	off_t offset = reg;
+	int rc;
+
+	rc = pwrite(astlpc->kcs_fd, &val, 1, offset);
+
+	return rc == 1 ? 0 : -1;
+}
+
+int mctp_astlpc_get_fd(struct mctp_binding_astlpc *astlpc)
+{
+	return astlpc->kcs_fd;
+}
+
+struct mctp_binding_astlpc *mctp_astlpc_init_fileio(void)
+{
+	struct mctp_binding_astlpc *astlpc;
+	int rc;
+
+	/*
+	 * If we're doing file IO then we're very likely not running
+	 * freestanding, so lets assume that we're on the BMC side.
+	 *
+	 * Requesting an MTU of 0 requests the largest possible MTU, whatever
+	 * value that might take.
+	 */
+	astlpc = __mctp_astlpc_init(MCTP_BINDING_ASTLPC_MODE_BMC, 0);
+	if (!astlpc)
+		return NULL;
+
+	/* Set internal operations for kcs. We use direct accesses to the lpc
+	 * map area */
+	astlpc->ops.kcs_read = __mctp_astlpc_fileio_kcs_read;
+	astlpc->ops.kcs_write = __mctp_astlpc_fileio_kcs_write;
+	astlpc->ops_data = astlpc;
+
+	rc = mctp_astlpc_init_fileio_lpc(astlpc);
+	if (rc) {
+		free(astlpc);
+		return NULL;
+	}
+
+	rc = mctp_astlpc_init_fileio_kcs(astlpc);
+	if (rc) {
+		free(astlpc);
+		return NULL;
+	}
+
+	return astlpc;
+}
+#else
+struct mctp_binding_astlpc * __attribute__((const))
+	mctp_astlpc_init_fileio(void)
+{
+	// Missing support for file IO
+	return NULL;
+}
+
+int __attribute__((const)) mctp_astlpc_get_fd(
+		struct mctp_binding_astlpc *astlpc __attribute__((unused)))
+{
+	astlpc_prerr(astlpc, "Missing support for file IO");
+	return -1;
+}
+#endif
diff --git a/libmctp/config.h b/libmctp/config.h
new file mode 100644
index 00000000..3f4a6511
--- /dev/null
+++ b/libmctp/config.h
@@ -0,0 +1,59 @@ 
+/* Define to 1 if you have the <dlfcn.h> header file. */
+#undef HAVE_DLFCN_H
+
+/* use the ccan endian conversion functions rather than the BSD ones */
+#define HAVE_ENDIAN_H 1
+
+/* Define to 1 if you have the <fcntl.h> header file. */
+#undef HAVE_FCNTL_H
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#undef HAVE_INTTYPES_H
+
+/* Define to 1 if you have the <memory.h> header file. */
+#undef HAVE_MEMORY_H
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#undef HAVE_STDLIB_H
+
+/* Define to 1 if you have the <strings.h> header file. */
+#undef HAVE_STRINGS_H
+
+/* Define to 1 if you have the <string.h> header file. */
+#undef HAVE_STRING_H
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#undef HAVE_SYS_STAT_H
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#undef HAVE_SYS_TYPES_H
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#undef HAVE_UNISTD_H
+
+/* Define to use libc malloc and free for heap memory.
+ *
+ * NB: Skiboot's malloc() is defined as a macro and can't
+ *     be used directly.
+ */
+#undef MCTP_DEFAULT_ALLOC
+
+/* Support interfaces based on file-descriptors */
+#undef MCTP_HAVE_FILEIO
+
+/* Define to enable stdio functions */
+#undef MCTP_HAVE_STDIO
+
+/* Define to enable syslog */
+#undef MCTP_HAVE_SYSLOG
+
+/* Define to 1 if you have the ANSI C header files. */
+#undef STDC_HEADERS
+
+
+#include <opal-api.h>
+/* Convert POSIX / Linux errno codes used by libmctp to OPAL return codes */
+#define EHOSTDOWN (-OPAL_CLOSED)
diff --git a/libmctp/container_of.h b/libmctp/container_of.h
new file mode 100644
index 00000000..a0e181a5
--- /dev/null
+++ b/libmctp/container_of.h
@@ -0,0 +1,9 @@ 
+#ifndef _CONTAINER_OF_H
+#define _CONTAINER_OF_H
+
+#ifndef container_of
+#define container_of(ptr, type, member)                                        \
+    (type *)((char *)(ptr) - (char *)&((type *)0)->member)
+#endif
+
+#endif
diff --git a/libmctp/core.c b/libmctp/core.c
new file mode 100644
index 00000000..c3ee659b
--- /dev/null
+++ b/libmctp/core.c
@@ -0,0 +1,833 @@ 
+/* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */
+
+#include <assert.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#undef pr_fmt
+#define pr_fmt(fmt) "core: " fmt
+
+#include "libmctp.h"
+#include "libmctp-alloc.h"
+#include "libmctp-log.h"
+#include "libmctp-cmds.h"
+#include "range.h"
+
+/* Internal data structures */
+
+enum mctp_bus_state {
+	mctp_bus_state_constructed = 0,
+	mctp_bus_state_tx_enabled,
+	mctp_bus_state_tx_disabled,
+};
+
+struct mctp_bus {
+	mctp_eid_t eid;
+	struct mctp_binding *binding;
+	enum mctp_bus_state state;
+
+	struct mctp_pktbuf *tx_queue_head;
+	struct mctp_pktbuf *tx_queue_tail;
+
+	/* todo: routing */
+};
+
+struct mctp_msg_ctx {
+	uint8_t		src;
+	uint8_t		dest;
+	uint8_t		tag;
+	uint8_t		last_seq;
+	void		*buf;
+	size_t		buf_size;
+	size_t		buf_alloc_size;
+	size_t		fragment_size;
+};
+
+struct mctp {
+	int			n_busses;
+	struct mctp_bus		*busses;
+
+	/* Message RX callback */
+	mctp_rx_fn		message_rx;
+	void			*message_rx_data;
+
+	/* Packet capture callback */
+	mctp_capture_fn		capture;
+	void			*capture_data;
+
+	/* Message reassembly.
+	 * @todo: flexible context count
+	 */
+	struct mctp_msg_ctx	msg_ctxs[16];
+
+	enum {
+		ROUTE_ENDPOINT,
+		ROUTE_BRIDGE,
+	}			route_policy;
+	size_t max_message_size;
+};
+
+#ifndef BUILD_ASSERT
+#define BUILD_ASSERT(x) \
+	do { (void)sizeof(char[0-(!(x))]); } while (0)
+#endif
+
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
+#endif
+
+/* 64kb should be sufficient for a single message. Applications
+ * requiring higher sizes can override by setting max_message_size.*/
+#ifndef MCTP_MAX_MESSAGE_SIZE
+#define MCTP_MAX_MESSAGE_SIZE 65536
+#endif
+
+static int mctp_message_tx_on_bus(struct mctp_bus *bus, mctp_eid_t src,
+				  mctp_eid_t dest, bool tag_owner,
+				  uint8_t msg_tag, void *msg, size_t msg_len);
+
+struct mctp_pktbuf *mctp_pktbuf_alloc(struct mctp_binding *binding, size_t len)
+{
+	struct mctp_pktbuf *buf;
+	size_t size;
+
+	size = binding->pkt_size + binding->pkt_header + binding->pkt_trailer;
+
+	/* todo: pools */
+	buf = __mctp_alloc(sizeof(*buf) + size);
+
+	buf->size = size;
+	buf->start = binding->pkt_header;
+	buf->end = buf->start + len;
+	buf->mctp_hdr_off = buf->start;
+	buf->next = NULL;
+
+	return buf;
+}
+
+void mctp_pktbuf_free(struct mctp_pktbuf *pkt)
+{
+	__mctp_free(pkt);
+}
+
+struct mctp_hdr *mctp_pktbuf_hdr(struct mctp_pktbuf *pkt)
+{
+	return (void *)pkt->data + pkt->mctp_hdr_off;
+}
+
+void *mctp_pktbuf_data(struct mctp_pktbuf *pkt)
+{
+	return (void *)pkt->data + pkt->mctp_hdr_off + sizeof(struct mctp_hdr);
+}
+
+size_t mctp_pktbuf_size(struct mctp_pktbuf *pkt)
+{
+	return pkt->end - pkt->start;
+}
+
+void *mctp_pktbuf_alloc_start(struct mctp_pktbuf *pkt, size_t size)
+{
+	assert(size <= pkt->start);
+	pkt->start -= size;
+	return pkt->data + pkt->start;
+}
+
+void *mctp_pktbuf_alloc_end(struct mctp_pktbuf *pkt, size_t size)
+{
+	void *buf;
+
+	assert(size <= (pkt->size - pkt->end));
+	buf = pkt->data + pkt->end;
+	pkt->end += size;
+	return buf;
+}
+
+int mctp_pktbuf_push(struct mctp_pktbuf *pkt, void *data, size_t len)
+{
+	void *p;
+
+	if (pkt->end + len > pkt->size)
+		return -1;
+
+	p = pkt->data + pkt->end;
+
+	pkt->end += len;
+	memcpy(p, data, len);
+
+	return 0;
+}
+
+void *mctp_pktbuf_pop(struct mctp_pktbuf *pkt, size_t len)
+{
+	if (len > mctp_pktbuf_size(pkt))
+		return NULL;
+
+	pkt->end -= len;
+	return pkt->data + pkt->end;
+}
+
+/* Message reassembly */
+static struct mctp_msg_ctx *mctp_msg_ctx_lookup(struct mctp *mctp,
+		uint8_t src, uint8_t dest, uint8_t tag)
+{
+	unsigned int i;
+
+	/* @todo: better lookup, if we add support for more outstanding
+	 * message contexts */
+	for (i = 0; i < ARRAY_SIZE(mctp->msg_ctxs); i++) {
+		struct mctp_msg_ctx *ctx = &mctp->msg_ctxs[i];
+		if (ctx->src == src && ctx->dest == dest && ctx->tag == tag)
+			return ctx;
+	}
+
+	return NULL;
+}
+
+static struct mctp_msg_ctx *mctp_msg_ctx_create(struct mctp *mctp,
+		uint8_t src, uint8_t dest, uint8_t tag)
+{
+	struct mctp_msg_ctx *ctx = NULL;
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(mctp->msg_ctxs); i++) {
+		struct mctp_msg_ctx *tmp = &mctp->msg_ctxs[i];
+		if (!tmp->src) {
+			ctx = tmp;
+			break;
+		}
+	}
+
+	if (!ctx)
+		return NULL;
+
+	ctx->src = src;
+	ctx->dest = dest;
+	ctx->tag = tag;
+	ctx->buf_size = 0;
+
+	return ctx;
+}
+
+static void mctp_msg_ctx_drop(struct mctp_msg_ctx *ctx)
+{
+	ctx->src = 0;
+}
+
+static void mctp_msg_ctx_reset(struct mctp_msg_ctx *ctx)
+{
+	ctx->buf_size = 0;
+	ctx->fragment_size = 0;
+}
+
+static int mctp_msg_ctx_add_pkt(struct mctp_msg_ctx *ctx,
+		struct mctp_pktbuf *pkt, size_t max_size)
+{
+	size_t len;
+
+	len = mctp_pktbuf_size(pkt) - sizeof(struct mctp_hdr);
+
+	if (len + ctx->buf_size < ctx->buf_size) {
+		return -1;
+	}
+
+	if (ctx->buf_size + len > ctx->buf_alloc_size) {
+		size_t new_alloc_size;
+		void *lbuf;
+
+		/* @todo: finer-grained allocation */
+		if (!ctx->buf_alloc_size) {
+			new_alloc_size = MAX(len, 4096UL);
+		} else {
+			new_alloc_size = MAX(ctx->buf_alloc_size * 2, len + ctx->buf_size);
+		}
+
+		/* Don't allow heap to grow beyond a limit */
+		if (new_alloc_size > max_size)
+			return -1;
+
+
+		lbuf = __mctp_realloc(ctx->buf, new_alloc_size);
+		if (lbuf) {
+			ctx->buf = lbuf;
+			ctx->buf_alloc_size = new_alloc_size;
+		} else {
+			__mctp_free(ctx->buf);
+			return -1;
+		}
+	}
+
+	memcpy(ctx->buf + ctx->buf_size, mctp_pktbuf_data(pkt), len);
+	ctx->buf_size += len;
+
+	return 0;
+}
+
+/* Core API functions */
+struct mctp *mctp_init(void)
+{
+	struct mctp *mctp;
+
+	mctp = __mctp_alloc(sizeof(*mctp));
+
+	if(!mctp)
+		return NULL;
+
+	memset(mctp, 0, sizeof(*mctp));
+	mctp->max_message_size = MCTP_MAX_MESSAGE_SIZE;
+
+	return mctp;
+}
+
+void mctp_set_max_message_size(struct mctp *mctp, size_t message_size)
+{
+	mctp->max_message_size = message_size;
+}
+
+void mctp_set_capture_handler(struct mctp *mctp, mctp_capture_fn fn, void *user)
+{
+	mctp->capture = fn;
+	mctp->capture_data = user;
+}
+
+static void mctp_bus_destroy(struct mctp_bus *bus)
+{
+	while (bus->tx_queue_head) {
+		struct mctp_pktbuf *curr = bus->tx_queue_head;
+
+		bus->tx_queue_head = curr->next;
+		mctp_pktbuf_free(curr);
+	}
+}
+
+void mctp_destroy(struct mctp *mctp)
+{
+	size_t i;
+
+	/* Cleanup message assembly contexts */
+	BUILD_ASSERT(ARRAY_SIZE(mctp->msg_ctxs) < SIZE_MAX);
+	for (i = 0; i < ARRAY_SIZE(mctp->msg_ctxs); i++) {
+		struct mctp_msg_ctx *tmp = &mctp->msg_ctxs[i];
+		if (tmp->buf)
+			__mctp_free(tmp->buf);
+	}
+
+	while (mctp->n_busses--)
+		mctp_bus_destroy(&mctp->busses[mctp->n_busses]);
+
+	__mctp_free(mctp->busses);
+	__mctp_free(mctp);
+}
+
+int mctp_set_rx_all(struct mctp *mctp, mctp_rx_fn fn, void *data)
+{
+	mctp->message_rx = fn;
+	mctp->message_rx_data = data;
+	return 0;
+}
+
+static struct mctp_bus *find_bus_for_eid(struct mctp *mctp,
+		mctp_eid_t dest __attribute__((unused)))
+{
+	if (mctp->n_busses == 0)
+		return NULL;
+
+	/* for now, just use the first bus. For full routing support,
+	 * we will need a table of neighbours */
+	return &mctp->busses[0];
+}
+
+int mctp_register_bus(struct mctp *mctp,
+		struct mctp_binding *binding,
+		mctp_eid_t eid)
+{
+	int rc = 0;
+
+	/* todo: multiple busses */
+	assert(mctp->n_busses == 0);
+	mctp->n_busses = 1;
+
+	mctp->busses = __mctp_alloc(sizeof(struct mctp_bus));
+	if (!mctp->busses)
+		return -ENOMEM;
+
+	memset(mctp->busses, 0, sizeof(struct mctp_bus));
+	mctp->busses[0].binding = binding;
+	mctp->busses[0].eid = eid;
+	binding->bus = &mctp->busses[0];
+	binding->mctp = mctp;
+	mctp->route_policy = ROUTE_ENDPOINT;
+
+	if (binding->start) {
+		rc = binding->start(binding);
+		if (rc < 0) {
+			mctp_prerr("Failed to start binding: %d", rc);
+			binding->bus = NULL;
+			__mctp_free(mctp->busses);
+			mctp->busses = NULL;
+			mctp->n_busses = 0;
+		}
+	}
+
+	return rc;
+}
+
+void mctp_unregister_bus(struct mctp *mctp, struct mctp_binding *binding)
+{
+	/*
+	 * We only support one bus right now; once the call completes we will
+	 * have no more busses
+	 */
+	mctp->n_busses = 0;
+	binding->mctp = NULL;
+	binding->bus = NULL;
+	free(mctp->busses);
+}
+
+int mctp_bridge_busses(struct mctp *mctp,
+		struct mctp_binding *b1, struct mctp_binding *b2)
+{
+	int rc = 0;
+
+	assert(mctp->n_busses == 0);
+	mctp->busses = __mctp_alloc(2 * sizeof(struct mctp_bus));
+	if (!mctp->busses)
+		return -ENOMEM;
+	memset(mctp->busses, 0, 2 * sizeof(struct mctp_bus));
+	mctp->n_busses = 2;
+	mctp->busses[0].binding = b1;
+	b1->bus = &mctp->busses[0];
+	b1->mctp = mctp;
+	mctp->busses[1].binding = b2;
+	b2->bus = &mctp->busses[1];
+	b2->mctp = mctp;
+
+	mctp->route_policy = ROUTE_BRIDGE;
+
+	if (b1->start) {
+		rc = b1->start(b1);
+		if (rc < 0) {
+			mctp_prerr("Failed to start bridged bus %s: %d",
+				   b1->name, rc);
+			goto done;
+		}
+	}
+
+	if (b2->start) {
+		rc = b2->start(b2);
+		if (rc < 0) {
+			mctp_prerr("Failed to start bridged bus %s: %d",
+				   b2->name, rc);
+			goto done;
+		}
+	}
+
+done:
+	return rc;
+}
+
+static inline bool mctp_ctrl_cmd_is_transport(struct mctp_ctrl_msg_hdr *hdr)
+{
+	return ((hdr->command_code >= MCTP_CTRL_CMD_FIRST_TRANSPORT) &&
+		(hdr->command_code <= MCTP_CTRL_CMD_LAST_TRANSPORT));
+}
+
+static bool mctp_ctrl_handle_msg(struct mctp_bus *bus, mctp_eid_t src,
+				 uint8_t msg_tag, bool tag_owner, void *buffer,
+				 size_t length)
+{
+	struct mctp_ctrl_msg_hdr *msg_hdr = buffer;
+
+	/*
+	 * Control message is received. If a transport control message handler
+	 * is provided, it will called. If there is no dedicated handler, this
+	 * function returns false and data can be handled by the generic
+	 * message handler. The transport control message handler will be
+	 * provided with messages in the command range 0xF0 - 0xFF.
+	 */
+	if (mctp_ctrl_cmd_is_transport(msg_hdr)) {
+		if (bus->binding->control_rx != NULL) {
+			/* MCTP bus binding handler */
+			bus->binding->control_rx(src, msg_tag, tag_owner,
+						 bus->binding->control_rx_data,
+						 buffer, length);
+			return true;
+		}
+	}
+
+	/*
+	 * Command was not handled, due to lack of specific callback.
+	 * It will be passed to regular message_rx handler.
+	 */
+	return false;
+}
+
+static inline bool mctp_rx_dest_is_local(struct mctp_bus *bus, mctp_eid_t dest)
+{
+	return dest == bus->eid || dest == MCTP_EID_NULL ||
+	       dest == MCTP_EID_BROADCAST;
+}
+
+static inline bool mctp_ctrl_cmd_is_request(struct mctp_ctrl_msg_hdr *hdr)
+{
+	return hdr->ic_msg_type == MCTP_CTRL_HDR_MSG_TYPE &&
+	       hdr->rq_dgram_inst & MCTP_CTRL_HDR_FLAG_REQUEST;
+}
+
+/*
+ * Receive the complete MCTP message and route it.
+ * Asserts:
+ *     'buf' is not NULL.
+ */
+static void mctp_rx(struct mctp *mctp, struct mctp_bus *bus, mctp_eid_t src,
+		    mctp_eid_t dest, bool tag_owner, uint8_t msg_tag, void *buf,
+		    size_t len)
+{
+	assert(buf != NULL);
+
+	if (mctp->route_policy == ROUTE_ENDPOINT &&
+	    mctp_rx_dest_is_local(bus, dest)) {
+		/* Handle MCTP Control Messages: */
+		if (len >= sizeof(struct mctp_ctrl_msg_hdr)) {
+			struct mctp_ctrl_msg_hdr *msg_hdr = buf;
+
+			/*
+			 * Identify if this is a control request message.
+			 * See DSP0236 v1.3.0 sec. 11.5.
+			 */
+			if (mctp_ctrl_cmd_is_request(msg_hdr)) {
+				bool handled;
+				handled = mctp_ctrl_handle_msg(
+					bus, src, msg_tag, tag_owner, buf, len);
+				if (handled)
+					return;
+			}
+		}
+
+		if (mctp->message_rx)
+			mctp->message_rx(src, tag_owner, msg_tag,
+					 mctp->message_rx_data, buf, len);
+	}
+
+	if (mctp->route_policy == ROUTE_BRIDGE) {
+		int i;
+
+		for (i = 0; i < mctp->n_busses; i++) {
+			struct mctp_bus *dest_bus = &mctp->busses[i];
+			if (dest_bus == bus)
+				continue;
+
+			mctp_message_tx_on_bus(dest_bus, src, dest, tag_owner,
+					       msg_tag, buf, len);
+		}
+
+	}
+}
+
+void mctp_bus_rx(struct mctp_binding *binding, struct mctp_pktbuf *pkt)
+{
+	struct mctp_bus *bus = binding->bus;
+	struct mctp *mctp = binding->mctp;
+	uint8_t flags, exp_seq, seq, tag;
+	struct mctp_msg_ctx *ctx;
+	struct mctp_hdr *hdr;
+	bool tag_owner;
+	size_t len;
+	void *p;
+	int rc;
+
+	assert(bus);
+
+	/* Drop packet if it was smaller than mctp hdr size */
+	if (mctp_pktbuf_size(pkt) <= sizeof(struct mctp_hdr))
+		goto out;
+
+	if (mctp->capture)
+		mctp->capture(pkt, mctp->capture_data);
+
+	hdr = mctp_pktbuf_hdr(pkt);
+
+	/* small optimisation: don't bother reassembly if we're going to
+	 * drop the packet in mctp_rx anyway */
+	if (mctp->route_policy == ROUTE_ENDPOINT && hdr->dest != bus->eid)
+		goto out;
+
+	flags = hdr->flags_seq_tag & (MCTP_HDR_FLAG_SOM | MCTP_HDR_FLAG_EOM);
+	tag = (hdr->flags_seq_tag >> MCTP_HDR_TAG_SHIFT) & MCTP_HDR_TAG_MASK;
+	seq = (hdr->flags_seq_tag >> MCTP_HDR_SEQ_SHIFT) & MCTP_HDR_SEQ_MASK;
+	tag_owner =
+		(hdr->flags_seq_tag >> MCTP_HDR_TO_SHIFT) & MCTP_HDR_TO_MASK;
+
+	switch (flags) {
+	case MCTP_HDR_FLAG_SOM | MCTP_HDR_FLAG_EOM:
+		/* single-packet message - send straight up to rx function,
+		 * no need to create a message context */
+		len = pkt->end - pkt->mctp_hdr_off - sizeof(struct mctp_hdr);
+		p = pkt->data + pkt->mctp_hdr_off + sizeof(struct mctp_hdr);
+		mctp_rx(mctp, bus, hdr->src, hdr->dest, tag_owner, tag, p, len);
+		break;
+
+	case MCTP_HDR_FLAG_SOM:
+		/* start of a new message - start the new context for
+		 * future message reception. If an existing context is
+		 * already present, drop it. */
+		ctx = mctp_msg_ctx_lookup(mctp, hdr->src, hdr->dest, tag);
+		if (ctx) {
+			mctp_msg_ctx_reset(ctx);
+		} else {
+			ctx = mctp_msg_ctx_create(mctp,
+					hdr->src, hdr->dest, tag);
+			/* If context creation fails due to exhaution of contexts we
+			* can support, drop the packet */
+			if (!ctx) {
+				mctp_prdebug("Context buffers exhausted.");
+				goto out;
+			}
+		}
+
+		/* Save the fragment size, subsequent middle fragments
+		 * should of the same size */
+		ctx->fragment_size = mctp_pktbuf_size(pkt);
+
+		rc = mctp_msg_ctx_add_pkt(ctx, pkt, mctp->max_message_size);
+		if (rc) {
+			mctp_msg_ctx_drop(ctx);
+		} else {
+			ctx->last_seq = seq;
+		}
+
+		break;
+
+	case MCTP_HDR_FLAG_EOM:
+		ctx = mctp_msg_ctx_lookup(mctp, hdr->src, hdr->dest, tag);
+		if (!ctx)
+			goto out;
+
+		exp_seq = (ctx->last_seq + 1) % 4;
+
+		if (exp_seq != seq) {
+			mctp_prdebug(
+				"Sequence number %d does not match expected %d",
+				seq, exp_seq);
+			mctp_msg_ctx_drop(ctx);
+			goto out;
+		}
+
+		len = mctp_pktbuf_size(pkt);
+
+		if (len > ctx->fragment_size) {
+			mctp_prdebug("Unexpected fragment size. Expected" \
+				" less than %zu, received = %zu",
+				ctx->fragment_size, len);
+			mctp_msg_ctx_drop(ctx);
+			goto out;
+		}
+
+		rc = mctp_msg_ctx_add_pkt(ctx, pkt, mctp->max_message_size);
+		if (!rc)
+			mctp_rx(mctp, bus, ctx->src, ctx->dest, tag_owner, tag,
+				ctx->buf, ctx->buf_size);
+
+		mctp_msg_ctx_drop(ctx);
+		break;
+
+	case 0:
+		/* Neither SOM nor EOM */
+		ctx = mctp_msg_ctx_lookup(mctp, hdr->src,hdr->dest, tag);
+		if (!ctx)
+			goto out;
+
+		exp_seq = (ctx->last_seq + 1) % 4;
+		if (exp_seq != seq) {
+			mctp_prdebug(
+				"Sequence number %d does not match expected %d",
+				seq, exp_seq);
+			mctp_msg_ctx_drop(ctx);
+			goto out;
+		}
+
+		len = mctp_pktbuf_size(pkt);
+
+		if (len != ctx->fragment_size) {
+			mctp_prdebug("Unexpected fragment size. Expected = %zu " \
+				"received = %zu", ctx->fragment_size, len);
+			mctp_msg_ctx_drop(ctx);
+			goto out;
+		}
+
+		rc = mctp_msg_ctx_add_pkt(ctx, pkt, mctp->max_message_size);
+		if (rc) {
+			mctp_msg_ctx_drop(ctx);
+			goto out;
+		}
+		ctx->last_seq = seq;
+
+		break;
+	}
+out:
+	mctp_pktbuf_free(pkt);
+}
+
+static int mctp_packet_tx(struct mctp_bus *bus,
+		struct mctp_pktbuf *pkt)
+{
+	struct mctp *mctp = bus->binding->mctp;
+
+	if (bus->state != mctp_bus_state_tx_enabled)
+		return -1;
+
+	if (mctp->capture)
+		mctp->capture(pkt, mctp->capture_data);
+
+	return bus->binding->tx(bus->binding, pkt);
+}
+
+static void mctp_send_tx_queue(struct mctp_bus *bus)
+{
+	struct mctp_pktbuf *pkt;
+
+	while ((pkt = bus->tx_queue_head)) {
+		int rc;
+
+		rc = mctp_packet_tx(bus, pkt);
+		if (rc)
+			break;
+
+		bus->tx_queue_head = pkt->next;
+		mctp_pktbuf_free(pkt);
+	}
+
+	if (!bus->tx_queue_head)
+		bus->tx_queue_tail = NULL;
+
+}
+
+void mctp_binding_set_tx_enabled(struct mctp_binding *binding, bool enable)
+{
+	struct mctp_bus *bus = binding->bus;
+
+	switch(bus->state) {
+	case mctp_bus_state_constructed:
+		if (!enable)
+			return;
+
+		if (binding->pkt_size < MCTP_PACKET_SIZE(MCTP_BTU)) {
+			mctp_prerr("Cannot start %s binding with invalid MTU: %zu",
+				   binding->name,
+				   MCTP_BODY_SIZE(binding->pkt_size));
+			return;
+		}
+
+		bus->state = mctp_bus_state_tx_enabled;
+		mctp_prinfo("%s binding started", binding->name);
+		return;
+	case mctp_bus_state_tx_enabled:
+		if (enable)
+			return;
+
+		bus->state = mctp_bus_state_tx_disabled;
+		mctp_prdebug("%s binding Tx disabled", binding->name);
+		return;
+	case mctp_bus_state_tx_disabled:
+		if (!enable)
+			return;
+
+		bus->state = mctp_bus_state_tx_enabled;
+		mctp_prdebug("%s binding Tx enabled", binding->name);
+		mctp_send_tx_queue(bus);
+		return;
+	}
+}
+
+static int mctp_message_tx_on_bus(struct mctp_bus *bus, mctp_eid_t src,
+				  mctp_eid_t dest, bool tag_owner,
+				  uint8_t msg_tag, void *msg, size_t msg_len)
+{
+	size_t max_payload_len, payload_len, p;
+	struct mctp_pktbuf *pkt;
+	struct mctp_hdr *hdr;
+	int i;
+
+	if (bus->state == mctp_bus_state_constructed)
+		return -ENXIO;
+
+	if ((msg_tag & MCTP_HDR_TAG_MASK) != msg_tag)
+		return -EINVAL;
+
+	max_payload_len = MCTP_BODY_SIZE(bus->binding->pkt_size);
+
+	{
+		const bool valid_mtu = max_payload_len >= MCTP_BTU;
+		assert(valid_mtu);
+		if (!valid_mtu)
+			return -EINVAL;
+	}
+
+	mctp_prdebug("%s: Generating packets for transmission of %zu byte message from %hhu to %hhu",
+		     __func__, msg_len, src, dest);
+
+	/* queue up packets, each of max MCTP_MTU size */
+	for (p = 0, i = 0; p < msg_len; i++) {
+		payload_len = msg_len - p;
+		if (payload_len > max_payload_len)
+			payload_len = max_payload_len;
+
+		pkt = mctp_pktbuf_alloc(bus->binding,
+				payload_len + sizeof(*hdr));
+		hdr = mctp_pktbuf_hdr(pkt);
+
+		hdr->ver = bus->binding->version & 0xf;
+		hdr->dest = dest;
+		hdr->src = src;
+		hdr->flags_seq_tag = (tag_owner << MCTP_HDR_TO_SHIFT) |
+				     (msg_tag << MCTP_HDR_TAG_SHIFT);
+
+		if (i == 0)
+			hdr->flags_seq_tag |= MCTP_HDR_FLAG_SOM;
+		if (p + payload_len >= msg_len)
+			hdr->flags_seq_tag |= MCTP_HDR_FLAG_EOM;
+		hdr->flags_seq_tag |=
+			(i & MCTP_HDR_SEQ_MASK) << MCTP_HDR_SEQ_SHIFT;
+
+		memcpy(mctp_pktbuf_data(pkt), msg + p, payload_len);
+
+		/* add to tx queue */
+		if (bus->tx_queue_tail)
+			bus->tx_queue_tail->next = pkt;
+		else
+			bus->tx_queue_head = pkt;
+		bus->tx_queue_tail = pkt;
+
+		p += payload_len;
+	}
+
+	mctp_prdebug("%s: Enqueued %d packets", __func__, i);
+
+	mctp_send_tx_queue(bus);
+
+	return 0;
+}
+
+int mctp_message_tx(struct mctp *mctp, mctp_eid_t eid, bool tag_owner,
+		    uint8_t msg_tag, void *msg, size_t msg_len)
+{
+	struct mctp_bus *bus;
+
+	/* TODO: Protect against same tag being used across
+	 * different callers */
+	if ((msg_tag & MCTP_HDR_TAG_MASK) != msg_tag) {
+		mctp_prerr("Incorrect message tag %u passed.", msg_tag);
+		return -EINVAL;
+	}
+
+	bus = find_bus_for_eid(mctp, eid);
+	if (!bus)
+		return 0;
+
+	return mctp_message_tx_on_bus(bus, bus->eid, eid, tag_owner, msg_tag,
+				      msg, msg_len);
+}
diff --git a/libmctp/crc32.c b/libmctp/crc32.c
new file mode 100644
index 00000000..b5883c28
--- /dev/null
+++ b/libmctp/crc32.c
@@ -0,0 +1,25 @@ 
+/* SPDX-License-Identifier: Apache-2.0 */
+/* Copyright 2021 IBM Corp. */
+
+#include "crc32.h"
+
+#include <limits.h>
+
+/* Very dumb CRC-32 implementation */
+uint32_t crc32(const void *buf, size_t len)
+{
+	const uint8_t *buf8 = buf;
+	uint32_t rem = 0xffffffff;
+
+	for (; len; len--) {
+		int i;
+
+		rem = rem ^ *buf8;
+		for (i = 0; i < CHAR_BIT; i++)
+			rem = (rem >> 1) ^ ((rem & 1) * 0xEDB88320);
+
+		buf8++;
+	}
+
+	return rem ^ 0xffffffff;
+}
diff --git a/libmctp/crc32.h b/libmctp/crc32.h
new file mode 100644
index 00000000..c47a27ea
--- /dev/null
+++ b/libmctp/crc32.h
@@ -0,0 +1,9 @@ 
+#ifndef _CRC32_H
+#define _CRC32_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+uint32_t crc32(const void *buf, size_t len);
+
+#endif
diff --git a/libmctp/libmctp-alloc.h b/libmctp/libmctp-alloc.h
new file mode 100644
index 00000000..2532cfa9
--- /dev/null
+++ b/libmctp/libmctp-alloc.h
@@ -0,0 +1,12 @@ 
+/* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */
+
+#ifndef _LIBMCTP_ALLOC_H
+#define _LIBMCTP_ALLOC_H
+
+#include <stdlib.h>
+
+void *__mctp_alloc(size_t size);
+void __mctp_free(void *ptr);
+void *__mctp_realloc(void *ptr, size_t size);
+
+#endif /* _LIBMCTP_ALLOC_H */
diff --git a/libmctp/libmctp-astlpc.h b/libmctp/libmctp-astlpc.h
new file mode 100644
index 00000000..f324b902
--- /dev/null
+++ b/libmctp/libmctp-astlpc.h
@@ -0,0 +1,54 @@ 
+/* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */
+
+#ifndef _LIBMCTP_ASTLPCL_H
+#define _LIBMCTP_ASTLPCL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <libmctp.h>
+
+#include <stdint.h>
+
+struct mctp_binding_astlpc;
+
+/* todo: Remove enum from public interfaces */
+enum mctp_binding_astlpc_kcs_reg {
+	MCTP_ASTLPC_KCS_REG_DATA = 0,
+	MCTP_ASTLPC_KCS_REG_STATUS = 1,
+};
+
+struct mctp_binding_astlpc_ops {
+	int (*kcs_read)(void *data, enum mctp_binding_astlpc_kcs_reg reg,
+			uint8_t *val);
+	int (*kcs_write)(void *data, enum mctp_binding_astlpc_kcs_reg reg,
+			 uint8_t val);
+	int (*lpc_read)(void *data, void *buf, long offset, size_t len);
+	int (*lpc_write)(void *data, const void *buf, long offset, size_t len);
+};
+
+#define MCTP_BINDING_ASTLPC_MODE_BMC 0
+#define MCTP_BINDING_ASTLPC_MODE_HOST 1
+struct mctp_binding_astlpc *
+mctp_astlpc_init(uint8_t mode, uint32_t mtu, void *lpc_map,
+		 const struct mctp_binding_astlpc_ops *ops, void *ops_data);
+
+struct mctp_binding_astlpc *
+mctp_astlpc_init_ops(const struct mctp_binding_astlpc_ops *ops, void *ops_data,
+		     void *lpc_map);
+void mctp_astlpc_destroy(struct mctp_binding_astlpc *astlpc);
+
+struct mctp_binding *mctp_binding_astlpc_core(struct mctp_binding_astlpc *b);
+
+int mctp_astlpc_poll(struct mctp_binding_astlpc *astlpc);
+
+/* fileio-based interface */
+struct mctp_binding_astlpc *mctp_astlpc_init_fileio(void);
+int mctp_astlpc_get_fd(struct mctp_binding_astlpc *astlpc);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _LIBMCTP_ASTLPC_H */
diff --git a/libmctp/libmctp-cmds.h b/libmctp/libmctp-cmds.h
new file mode 100644
index 00000000..293aca4d
--- /dev/null
+++ b/libmctp/libmctp-cmds.h
@@ -0,0 +1,74 @@ 
+/* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */
+#ifndef _LIBMCTP_CMDS_H
+#define _LIBMCTP_CMDS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "libmctp.h"
+
+/*
+ * Helper structs and functions for MCTP control messages.
+ * See DSP0236 v1.3.0 sec. 11 for reference.
+ */
+
+struct mctp_ctrl_msg_hdr {
+	uint8_t ic_msg_type;
+	uint8_t rq_dgram_inst;
+	uint8_t command_code;
+	uint8_t completion_code;
+};
+
+#define MCTP_CTRL_HDR_MSG_TYPE	       0
+#define MCTP_CTRL_HDR_FLAG_REQUEST     (1 << 7)
+#define MCTP_CTRL_HDR_FLAG_DGRAM       (1 << 6)
+#define MCTP_CTRL_HDR_INSTANCE_ID_MASK 0x1F
+
+/*
+ * MCTP Control Command IDs
+ * See DSP0236 v1.3.0 Table 12.
+ */
+#define MCTP_CTRL_CMD_RESERVED			 0x00
+#define MCTP_CTRL_CMD_SET_ENDPOINT_ID		 0x01
+#define MCTP_CTRL_CMD_GET_ENDPOINT_ID		 0x02
+#define MCTP_CTRL_CMD_GET_ENDPOINT_UUID		 0x03
+#define MCTP_CTRL_CMD_GET_VERSION_SUPPORT	 0x04
+#define MCTP_CTRL_CMD_GET_MESSAGE_TYPE_SUPPORT	 0x05
+#define MCTP_CTRL_CMD_GET_VENDOR_MESSAGE_SUPPORT 0x06
+#define MCTP_CTRL_CMD_RESOLVE_ENDPOINT_ID	 0x07
+#define MCTP_CTRL_CMD_ALLOCATE_ENDPOINT_IDS	 0x08
+#define MCTP_CTRL_CMD_ROUTING_INFO_UPDATE	 0x09
+#define MCTP_CTRL_CMD_GET_ROUTING_TABLE_ENTRIES	 0x0A
+#define MCTP_CTRL_CMD_PREPARE_ENDPOINT_DISCOVERY 0x0B
+#define MCTP_CTRL_CMD_ENDPOINT_DISCOVERY	 0x0C
+#define MCTP_CTRL_CMD_DISCOVERY_NOTIFY		 0x0D
+#define MCTP_CTRL_CMD_GET_NETWORK_ID		 0x0E
+#define MCTP_CTRL_CMD_QUERY_HOP			 0x0F
+#define MCTP_CTRL_CMD_RESOLVE_UUID		 0x10
+#define MCTP_CTRL_CMD_QUERY_RATE_LIMIT		 0x11
+#define MCTP_CTRL_CMD_REQUEST_TX_RATE_LIMIT	 0x12
+#define MCTP_CTRL_CMD_UPDATE_RATE_LIMIT		 0x13
+#define MCTP_CTRL_CMD_QUERY_SUPPORTED_INTERFACES 0x14
+#define MCTP_CTRL_CMD_MAX			 0x15
+/* 0xF0 - 0xFF are transport specific */
+#define MCTP_CTRL_CMD_FIRST_TRANSPORT 0xF0
+#define MCTP_CTRL_CMD_LAST_TRANSPORT  0xFF
+
+/*
+ * MCTP Control Completion Codes
+ * See DSP0236 v1.3.0 Table 13.
+ */
+#define MCTP_CTRL_CC_SUCCESS		   0x00
+#define MCTP_CTRL_CC_ERROR		   0x01
+#define MCTP_CTRL_CC_ERROR_INVALID_DATA	   0x02
+#define MCTP_CTRL_CC_ERROR_INVALID_LENGTH  0x03
+#define MCTP_CTRL_CC_ERROR_NOT_READY	   0x04
+#define MCTP_CTRL_CC_ERROR_UNSUPPORTED_CMD 0x05
+/* 0x80 - 0xFF are command specific */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _LIBMCTP_CMDS_H */
diff --git a/libmctp/libmctp-log.h b/libmctp/libmctp-log.h
new file mode 100644
index 00000000..1e574e5e
--- /dev/null
+++ b/libmctp/libmctp-log.h
@@ -0,0 +1,24 @@ 
+/* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */
+
+#ifndef _LIBMCTP_LOG_H
+#define _LIBMCTP_LOG_H
+
+/* libmctp-internal logging */
+
+void mctp_prlog(int level, const char *fmt, ...)
+	__attribute__((format(printf, 2, 3)));
+
+#ifndef pr_fmt
+#define pr_fmt(x) x
+#endif
+
+#define mctp_prerr(fmt, ...)                                                   \
+	mctp_prlog(MCTP_LOG_ERR, pr_fmt(fmt), ##__VA_ARGS__)
+#define mctp_prwarn(fmt, ...)                                                  \
+	mctp_prlog(MCTP_LOG_WARNING, pr_fmt(fmt), ##__VA_ARGS__)
+#define mctp_prinfo(fmt, ...)                                                  \
+	mctp_prlog(MCTP_LOG_INFO, pr_fmt(fmt), ##__VA_ARGS__)
+#define mctp_prdebug(fmt, ...)                                                 \
+	mctp_prlog(MCTP_LOG_DEBUG, pr_fmt(fmt), ##__VA_ARGS__)
+
+#endif /* _LIBMCTP_LOG_H */
diff --git a/libmctp/libmctp-serial.h b/libmctp/libmctp-serial.h
new file mode 100644
index 00000000..8a70101e
--- /dev/null
+++ b/libmctp/libmctp-serial.h
@@ -0,0 +1,38 @@ 
+/* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */
+
+#ifndef _LIBMCTP_SERIAL_H
+#define _LIBMCTP_SERIAL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <libmctp.h>
+
+struct mctp_binding_serial;
+
+struct mctp_binding_serial *mctp_serial_init(void);
+void mctp_serial_destroy(struct mctp_binding_serial *serial);
+
+struct mctp_binding *mctp_binding_serial_core(struct mctp_binding_serial *b);
+
+/* file-based IO */
+int mctp_serial_get_fd(struct mctp_binding_serial *serial);
+int mctp_serial_read(struct mctp_binding_serial *serial);
+int mctp_serial_open_path(struct mctp_binding_serial *serial,
+		const char *path);
+void mctp_serial_open_fd(struct mctp_binding_serial *serial, int fd);
+
+/* direct function call IO */
+typedef int (*mctp_serial_tx_fn)(void *data, void *buf, size_t len)
+	__attribute__((warn_unused_result));
+void mctp_serial_set_tx_fn(struct mctp_binding_serial *serial,
+		mctp_serial_tx_fn fn, void *data);
+int mctp_serial_rx(struct mctp_binding_serial *serial,
+		const void *buf, size_t len);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _LIBMCTP_SERIAL_H */
diff --git a/libmctp/libmctp.h b/libmctp/libmctp.h
new file mode 100644
index 00000000..497d536e
--- /dev/null
+++ b/libmctp/libmctp.h
@@ -0,0 +1,156 @@ 
+/* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */
+
+#ifndef _LIBMCTP_H
+#define _LIBMCTP_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stddef.h>
+
+typedef uint8_t mctp_eid_t;
+
+/* Special Endpoint ID values */
+#define MCTP_EID_NULL 0
+#define MCTP_EID_BROADCAST 0xff
+
+/* MCTP packet definitions */
+struct mctp_hdr {
+	uint8_t	ver;
+	uint8_t	dest;
+	uint8_t	src;
+	uint8_t	flags_seq_tag;
+};
+
+/* Definitions for flags_seq_tag field */
+#define MCTP_HDR_FLAG_SOM  (1 << 7)
+#define MCTP_HDR_FLAG_EOM  (1 << 6)
+#define MCTP_HDR_FLAG_TO   (1 << 3)
+#define MCTP_HDR_TO_SHIFT  (3)
+#define MCTP_HDR_TO_MASK   (1)
+#define MCTP_HDR_SEQ_SHIFT (4)
+#define MCTP_HDR_SEQ_MASK  (0x3)
+#define MCTP_HDR_TAG_SHIFT (0)
+#define MCTP_HDR_TAG_MASK  (0x7)
+
+#define MCTP_MESSAGE_TO_SRC true
+#define MCTP_MESSAGE_TO_DST false
+
+/* Baseline Transmission Unit and packet size */
+#define MCTP_BTU		64
+#define MCTP_PACKET_SIZE(unit)	((unit) + sizeof(struct mctp_hdr))
+#define MCTP_BODY_SIZE(unit)	((unit) - sizeof(struct mctp_hdr))
+
+/* packet buffers */
+
+struct mctp_pktbuf {
+	size_t		start, end, size;
+	size_t		mctp_hdr_off;
+	struct mctp_pktbuf *next;
+	unsigned char	data[];
+};
+
+struct mctp_binding;
+
+struct mctp_pktbuf *mctp_pktbuf_alloc(struct mctp_binding *hw, size_t len);
+void mctp_pktbuf_free(struct mctp_pktbuf *pkt);
+struct mctp_hdr *mctp_pktbuf_hdr(struct mctp_pktbuf *pkt);
+void *mctp_pktbuf_data(struct mctp_pktbuf *pkt);
+size_t mctp_pktbuf_size(struct mctp_pktbuf *pkt);
+void *mctp_pktbuf_alloc_start(struct mctp_pktbuf *pkt, size_t size);
+void *mctp_pktbuf_alloc_end(struct mctp_pktbuf *pkt, size_t size);
+int mctp_pktbuf_push(struct mctp_pktbuf *pkt, void *data, size_t len);
+void *mctp_pktbuf_pop(struct mctp_pktbuf *pkt, size_t len);
+
+/* MCTP core */
+struct mctp;
+struct mctp_bus;
+
+struct mctp *mctp_init(void);
+void mctp_set_max_message_size(struct mctp *mctp, size_t message_size);
+typedef void (*mctp_capture_fn)(struct mctp_pktbuf *pkt, void *user);
+void mctp_set_capture_handler(struct mctp *mctp, mctp_capture_fn fn, void *user);
+void mctp_destroy(struct mctp *mctp);
+
+/* Register a binding to the MCTP core, and creates a bus (populating
+ * binding->bus).
+ *
+ * If this function is called, the MCTP stack is initialised as an 'endpoint',
+ * and will deliver local packets to a RX callback - see `mctp_set_rx_all()`
+ * below.
+ */
+int mctp_register_bus(struct mctp *mctp,
+		struct mctp_binding *binding,
+		mctp_eid_t eid);
+
+void mctp_unregister_bus(struct mctp *mctp, struct mctp_binding *binding);
+
+/* Create a simple bidirectional bridge between busses.
+ *
+ * In this mode, the MCTP stack is initialised as a bridge. There is no EID
+ * defined, so no packets are considered local. Instead, all messages from one
+ * binding are forwarded to the other.
+ */
+int mctp_bridge_busses(struct mctp *mctp,
+		struct mctp_binding *b1, struct mctp_binding *b2);
+
+typedef void (*mctp_rx_fn)(uint8_t src_eid, bool tag_owner, uint8_t msg_tag,
+			   void *data, void *msg, size_t len);
+
+int mctp_set_rx_all(struct mctp *mctp, mctp_rx_fn fn, void *data);
+
+int mctp_message_tx(struct mctp *mctp, mctp_eid_t eid, bool tag_owner,
+		    uint8_t msg_tag, void *msg, size_t msg_len);
+
+/* hardware bindings */
+struct mctp_binding {
+	const char *name;
+	uint8_t version;
+	struct mctp_bus *bus;
+	struct mctp *mctp;
+	size_t pkt_size;
+	size_t pkt_header;
+	size_t pkt_trailer;
+	int (*start)(struct mctp_binding *binding);
+	int (*tx)(struct mctp_binding *binding, struct mctp_pktbuf *pkt);
+	mctp_rx_fn control_rx;
+	void *control_rx_data;
+};
+
+void mctp_binding_set_tx_enabled(struct mctp_binding *binding, bool enable);
+
+/*
+ * Receive a packet from binding to core. Takes ownership of pkt, free()-ing it
+ * after use.
+ */
+void mctp_bus_rx(struct mctp_binding *binding, struct mctp_pktbuf *pkt);
+
+/* environment-specific allocation */
+void mctp_set_alloc_ops(void *(*alloc)(size_t),
+		void (*free)(void *),
+		void *(realloc)(void *, size_t));
+
+/* environment-specific logging */
+
+void mctp_set_log_stdio(int level);
+void mctp_set_log_syslog(void);
+void mctp_set_log_custom(void (*fn)(int, const char *, va_list));
+
+/* these should match the syslog-standard LOG_* definitions, for
+ * easier use with syslog */
+#define MCTP_LOG_ERR		3
+#define MCTP_LOG_WARNING	4
+#define MCTP_LOG_NOTICE		5
+#define MCTP_LOG_INFO		6
+#define MCTP_LOG_DEBUG		7
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _LIBMCTP_H */
diff --git a/libmctp/log.c b/libmctp/log.c
new file mode 100644
index 00000000..cbdbbcaa
--- /dev/null
+++ b/libmctp/log.c
@@ -0,0 +1,75 @@ 
+/* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */
+
+#include <stdarg.h>
+
+#include "libmctp.h"
+#include "libmctp-log.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef MCTP_HAVE_STDIO
+#include <stdio.h>
+#endif
+
+#ifdef MCTP_HAVE_SYSLOG
+#include <syslog.h>
+#endif
+
+enum {
+	MCTP_LOG_NONE,
+	MCTP_LOG_STDIO,
+	MCTP_LOG_SYSLOG,
+	MCTP_LOG_CUSTOM,
+} log_type = MCTP_LOG_NONE;
+
+static int log_stdio_level;
+static void (*log_custom_fn)(int, const char *, va_list);
+
+void mctp_prlog(int level, const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+
+	switch (log_type) {
+	case MCTP_LOG_NONE:
+		break;
+	case MCTP_LOG_STDIO:
+#ifdef MCTP_HAVE_STDIO
+		if (level <= log_stdio_level) {
+			vfprintf(stderr, fmt, ap);
+			fputs("\n", stderr);
+		}
+#endif
+		break;
+	case MCTP_LOG_SYSLOG:
+#ifdef MCTP_HAVE_SYSLOG
+		vsyslog(level, fmt, ap);
+#endif
+		break;
+	case MCTP_LOG_CUSTOM:
+		log_custom_fn(level, fmt, ap);
+		break;
+	}
+
+	va_end(ap);
+}
+
+void mctp_set_log_stdio(int level)
+{
+	log_type = MCTP_LOG_STDIO;
+	log_stdio_level = level;
+}
+
+void mctp_set_log_syslog(void)
+{
+	log_type = MCTP_LOG_SYSLOG;
+}
+
+void mctp_set_log_custom(void (*fn)(int, const char *, va_list))
+{
+	log_type = MCTP_LOG_CUSTOM;
+	log_custom_fn = fn;
+}
diff --git a/libmctp/range.h b/libmctp/range.h
new file mode 100644
index 00000000..6c960158
--- /dev/null
+++ b/libmctp/range.h
@@ -0,0 +1,18 @@ 
+#ifndef _RANGE_H
+#define _RANGE_H
+
+#define MIN(a, b)                                                              \
+	({                                                                     \
+		typeof(a) _a = a;                                              \
+		typeof(b) _b = b;                                              \
+		_a < _b ? _a : _b;                                             \
+	})
+
+#define MAX(a, b)                                                              \
+	({                                                                     \
+		typeof(a) _a = a;                                              \
+		typeof(b) _b = b;                                              \
+		_a > _b ? _a : _b;                                             \
+	})
+
+#endif
diff --git a/libmctp/serial.c b/libmctp/serial.c
new file mode 100644
index 00000000..ac91eb4a
--- /dev/null
+++ b/libmctp/serial.c
@@ -0,0 +1,359 @@ 
+/* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef MCTP_HAVE_FILEIO
+#include <unistd.h>
+#include <fcntl.h>
+#else
+static const size_t write(int fd, void *buf, size_t len)
+{
+	return -1;
+}
+#endif
+
+#define pr_fmt(x) "serial: " x
+
+/* Post-condition: All bytes written or an error has occurred */
+#define mctp_write_all(fn, dst, src, len)				\
+({									\
+	ssize_t wrote;							\
+	while (len) {							\
+		wrote = fn(dst, src, len);				\
+		if (wrote < 0)						\
+			break;						\
+		len -= wrote;						\
+	}								\
+	len ? -1 : 0;							\
+})
+
+#include "libmctp.h"
+#include "libmctp-alloc.h"
+#include "libmctp-log.h"
+#include "libmctp-serial.h"
+#include "container_of.h"
+
+struct mctp_binding_serial {
+	struct mctp_binding	binding;
+	int			fd;
+	unsigned long		bus_id;
+
+	mctp_serial_tx_fn	tx_fn;
+	void			*tx_fn_data;
+
+	/* receive buffer and state */
+	uint8_t			rxbuf[1024];
+	struct mctp_pktbuf	*rx_pkt;
+	uint8_t			rx_exp_len;
+	uint16_t		rx_fcs;
+	enum {
+		STATE_WAIT_SYNC_START,
+		STATE_WAIT_REVISION,
+		STATE_WAIT_LEN,
+		STATE_DATA,
+		STATE_DATA_ESCAPED,
+		STATE_WAIT_FCS1,
+		STATE_WAIT_FCS2,
+		STATE_WAIT_SYNC_END,
+	} rx_state;
+
+	/* temporary transmit buffer */
+	uint8_t			txbuf[256];
+};
+
+#define binding_to_serial(b) \
+	container_of(b, struct mctp_binding_serial, binding)
+
+#define MCTP_SERIAL_REVISION		0x01
+#define MCTP_SERIAL_FRAMING_FLAG	0x7e
+#define MCTP_SERIAL_ESCAPE		0x7d
+
+struct mctp_serial_header {
+	uint8_t	flag;
+	uint8_t revision;
+	uint8_t	len;
+};
+
+struct mctp_serial_trailer {
+	uint8_t	fcs_msb;
+	uint8_t fcs_lsb;
+	uint8_t	flag;
+};
+
+static size_t mctp_serial_pkt_escape(struct mctp_pktbuf *pkt, uint8_t *buf)
+{
+	uint8_t total_len;
+	uint8_t *p;
+	int i, j;
+
+	total_len = pkt->end - pkt->mctp_hdr_off;
+
+	p = (void *)mctp_pktbuf_hdr(pkt);
+
+	for (i = 0, j = 0; i < total_len; i++, j++) {
+		uint8_t c = p[i];
+		if (c == 0x7e || c == 0x7d) {
+			if (buf)
+				buf[j] = 0x7d;
+			j++;
+			c ^= 0x20;
+		}
+		if (buf)
+			buf[j] = c;
+	}
+
+	return j;
+}
+
+static int mctp_binding_serial_tx(struct mctp_binding *b,
+		struct mctp_pktbuf *pkt)
+{
+	struct mctp_binding_serial *serial = binding_to_serial(b);
+	struct mctp_serial_header *hdr;
+	struct mctp_serial_trailer *tlr;
+	uint8_t *buf;
+	size_t len;
+
+	/* the length field in the header excludes serial framing
+	 * and escape sequences */
+	len = mctp_pktbuf_size(pkt);
+
+	hdr = (void *)serial->txbuf;
+	hdr->flag = MCTP_SERIAL_FRAMING_FLAG;
+	hdr->revision = MCTP_SERIAL_REVISION;
+	hdr->len = len;
+
+	buf = (void *)(hdr + 1);
+
+	len = mctp_serial_pkt_escape(pkt, NULL);
+	if (len + sizeof(*hdr) + sizeof(*tlr) > sizeof(serial->txbuf))
+		return -1;
+
+	mctp_serial_pkt_escape(pkt, buf);
+
+	buf += len;
+
+	tlr = (void *)buf;
+	tlr->flag = MCTP_SERIAL_FRAMING_FLAG;
+	/* todo: trailer FCS */
+	tlr->fcs_msb = 0;
+	tlr->fcs_lsb = 0;
+
+	len += sizeof(*hdr) + sizeof(*tlr);
+
+	if (!serial->tx_fn)
+		return mctp_write_all(write, serial->fd, serial->txbuf, len);
+
+	return mctp_write_all(serial->tx_fn, serial->tx_fn_data, serial->txbuf,
+			      len);
+}
+
+static void mctp_serial_finish_packet(struct mctp_binding_serial *serial,
+		bool valid)
+{
+	struct mctp_pktbuf *pkt = serial->rx_pkt;
+	assert(pkt);
+
+	if (valid)
+		mctp_bus_rx(&serial->binding, pkt);
+
+	serial->rx_pkt = NULL;
+}
+
+static void mctp_serial_start_packet(struct mctp_binding_serial *serial,
+		uint8_t len)
+{
+	serial->rx_pkt = mctp_pktbuf_alloc(&serial->binding, len);
+}
+
+static void mctp_rx_consume_one(struct mctp_binding_serial *serial,
+		uint8_t c)
+{
+	struct mctp_pktbuf *pkt = serial->rx_pkt;
+
+	mctp_prdebug("state: %d, char 0x%02x", serial->rx_state, c);
+
+	assert(!pkt == (serial->rx_state == STATE_WAIT_SYNC_START ||
+			serial->rx_state == STATE_WAIT_REVISION ||
+			serial->rx_state == STATE_WAIT_LEN));
+
+	switch (serial->rx_state) {
+	case STATE_WAIT_SYNC_START:
+		if (c != MCTP_SERIAL_FRAMING_FLAG) {
+			mctp_prdebug("lost sync, dropping packet");
+			if (pkt)
+				mctp_serial_finish_packet(serial, false);
+		} else {
+			serial->rx_state = STATE_WAIT_REVISION;
+		}
+		break;
+
+	case STATE_WAIT_REVISION:
+		if (c == MCTP_SERIAL_REVISION) {
+			serial->rx_state = STATE_WAIT_LEN;
+		} else {
+			mctp_prdebug("invalid revision 0x%02x", c);
+			serial->rx_state = STATE_WAIT_SYNC_START;
+		}
+		break;
+	case STATE_WAIT_LEN:
+		if (c > serial->binding.pkt_size ||
+				c < sizeof(struct mctp_hdr)) {
+			mctp_prdebug("invalid size %d", c);
+			serial->rx_state = STATE_WAIT_SYNC_START;
+		} else {
+			mctp_serial_start_packet(serial, 0);
+			pkt = serial->rx_pkt;
+			serial->rx_exp_len = c;
+			serial->rx_state = STATE_DATA;
+		}
+		break;
+
+	case STATE_DATA:
+		if (c == MCTP_SERIAL_ESCAPE) {
+			serial->rx_state = STATE_DATA_ESCAPED;
+		} else {
+			mctp_pktbuf_push(pkt, &c, 1);
+			if (pkt->end - pkt->mctp_hdr_off == serial->rx_exp_len)
+				serial->rx_state = STATE_WAIT_FCS1;
+		}
+		break;
+
+	case STATE_DATA_ESCAPED:
+		c ^= 0x20;
+		mctp_pktbuf_push(pkt, &c, 1);
+		if (pkt->end - pkt->mctp_hdr_off == serial->rx_exp_len)
+			serial->rx_state = STATE_WAIT_FCS1;
+		else
+			serial->rx_state = STATE_DATA;
+		break;
+
+	case STATE_WAIT_FCS1:
+		serial->rx_fcs = c << 8;
+		serial->rx_state = STATE_WAIT_FCS2;
+		break;
+	case STATE_WAIT_FCS2:
+		serial->rx_fcs |= c;
+		/* todo: check fcs */
+		serial->rx_state = STATE_WAIT_SYNC_END;
+		break;
+
+	case STATE_WAIT_SYNC_END:
+		if (c == MCTP_SERIAL_FRAMING_FLAG) {
+			mctp_serial_finish_packet(serial, true);
+		} else {
+			mctp_prdebug("missing end frame marker");
+			mctp_serial_finish_packet(serial, false);
+		}
+		serial->rx_state = STATE_WAIT_SYNC_START;
+		break;
+	}
+
+	mctp_prdebug(" -> state: %d", serial->rx_state);
+}
+static void mctp_rx_consume(struct mctp_binding_serial *serial,
+		const void *buf, size_t len)
+{
+	size_t i;
+
+	for (i = 0; i < len; i++)
+		mctp_rx_consume_one(serial, *(uint8_t *)(buf + i));
+}
+
+#ifdef MCTP_HAVE_FILEIO
+int mctp_serial_read(struct mctp_binding_serial *serial)
+{
+	ssize_t len;
+
+	len = read(serial->fd, serial->rxbuf, sizeof(serial->rxbuf));
+	if (len == 0)
+		return -1;
+
+	if (len < 0) {
+		mctp_prerr("can't read from serial device: %m");
+		return -1;
+	}
+
+	mctp_rx_consume(serial, serial->rxbuf, len);
+
+	return 0;
+}
+
+int mctp_serial_get_fd(struct mctp_binding_serial *serial)
+{
+	return serial->fd;
+}
+
+int mctp_serial_open_path(struct mctp_binding_serial *serial,
+		const char *device)
+{
+	serial->fd = open(device, O_RDWR);
+	if (serial->fd < 0)
+		mctp_prerr("can't open device %s: %m", device);
+
+	return 0;
+}
+
+void mctp_serial_open_fd(struct mctp_binding_serial *serial, int fd)
+{
+	serial->fd = fd;
+}
+#endif
+
+void mctp_serial_set_tx_fn(struct mctp_binding_serial *serial,
+		mctp_serial_tx_fn fn, void *data)
+{
+	serial->tx_fn = fn;
+	serial->tx_fn_data = data;
+}
+
+int mctp_serial_rx(struct mctp_binding_serial *serial,
+		const void *buf, size_t len)
+{
+	mctp_rx_consume(serial, buf, len);
+	return 0;
+}
+
+static int mctp_serial_core_start(struct mctp_binding *binding)
+{
+	mctp_binding_set_tx_enabled(binding, true);
+	return 0;
+}
+
+struct mctp_binding *mctp_binding_serial_core(struct mctp_binding_serial *b)
+{
+	return &b->binding;
+}
+
+struct mctp_binding_serial *mctp_serial_init(void)
+{
+	struct mctp_binding_serial *serial;
+
+	serial = __mctp_alloc(sizeof(*serial));
+	memset(serial, 0, sizeof(*serial));
+	serial->fd = -1;
+	serial->rx_state = STATE_WAIT_SYNC_START;
+	serial->rx_pkt = NULL;
+	serial->binding.name = "serial";
+	serial->binding.version = 1;
+	serial->binding.pkt_size = MCTP_PACKET_SIZE(MCTP_BTU);
+	serial->binding.pkt_header = 0;
+	serial->binding.pkt_header = 0;
+
+	serial->binding.start = mctp_serial_core_start;
+	serial->binding.tx = mctp_binding_serial_tx;
+
+	return serial;
+}
+
+void mctp_serial_destroy(struct mctp_binding_serial *serial)
+{
+	__mctp_free(serial);
+}