diff --git a/Documentation/doc-guide/kernel-doc.rst b/Documentation/doc-guide/kernel-doc.rst
index 1dcbd7332476..554694a15586 100644
--- a/Documentation/doc-guide/kernel-doc.rst
+++ b/Documentation/doc-guide/kernel-doc.rst
@@ -93,6 +93,9 @@ The brief description following the function name may span multiple lines, and
 ends with an argument description, a blank comment line, or the end of the
 comment block.
 
+This may also be used to describe a functions in an assembly language file,
+provided that a Prototype line is also present (see below).
+
 Function parameters
 ~~~~~~~~~~~~~~~~~~~
 
@@ -171,6 +174,82 @@ named ``Return``.
      as a new section heading, which probably won't produce the desired
      effect.
 
+Prototypes
+~~~~~~~~~~
+
+In assembly language files (.S files), functions callable by
+C code are defined with::
+
+   SYM_FUNCTION_START(function_name)
+   assembly language code ...
+
+This does not list the arguments like a C function definition; that
+information is implicit in the assembly language instructions that follow.
+
+To document that usage and how the function should be referenced by
+C code, include the recommended Prototype like this::
+
+   /**
+    * crc_pcl - Calculate CRC32C using x86 CRC32 and PCLMULQDQ instructions
+    * @buffer:     address of data (%rdi, bufp macro)
+    * @len:        data size (%rsi, len macro)
+    * @crc_init:   initial CRC32C value (%rdx, crc_init_arg macro);
+    *              only using lower 32 bits
+    *
+    * This function supports 64-bit CPUs.
+    * It loops on 8-byte aligned QWORDs, but also supports unaligned
+    * addresses and all length values.
+    *
+    * Return:      CRC32C value (upper 32 bits zero)(%rax)
+    * Prototype:   asmlinkage unsigned int crc_pcl(const u8 *buffer,
+    *                                              unsigned int len,
+    *                                              unsigned int crc_init);
+    */
+   SYM_FUNC_START(crc_pcl)
+   assembly language code ...
+
+scripts/kernel-doc ensures that the arguments match those in the
+prototype and that the function name matches everywhere.
+
+Variants of SYM_FUNC_START like SYM_TYPED_FUNC_START and
+SYM_FUNC_START_WEAK are also supprted.
+
+In a few cases, a macro is defined that contains the SYM_FUNC_START()
+macro and code. scripts/kernel-doc recognizes that format as well::
+
+   .macro SHA1_VECTOR_ASM  name
+           SYM_FUNC_START(\name)
+           assembly language code ...
+
+   /**
+    * sha1_transform_ssse3 - Calculate SHA1 hash using the x86 SSSE3 feature set
+    * @digest:     address of current 20-byte hash value (%rdi, CTX macro)
+    * @data:       address of data (%rsi, BUF macro);
+    *              data size must be a multiple of 64 bytes
+    * @blocks:     number of 64-byte blocks (%rdx, CNT macro)
+    *
+    * This function supports 64-bit CPUs.
+    *
+    * Return:      none
+    * Prototype:   asmlinkage void sha1_transform_ssse3(u32 *digest, const u8 *data, int blocks)
+    */
+   SHA1_VECTOR_ASM     sha1_transform_ssse3
+
+   /**
+    * sha1_transform_avx - Calculate SHA1 hash using the x86 AVX feature set
+    * @digest:     address of current 20-byte hash value (%rdi, CTX macro)
+    * @data:       address of data (%rsi, BUF macro);
+    *              data size must be a multiple of 64 bytes
+    * @blocks:     number of 64-byte blocks (%rdx, CNT macro)
+    *
+    * This function supports 64-bit CPUs.
+    *
+    * Return:      none
+    * Prototype:   asmlinkage void sha1_transform_avx(u32 *digest, const u8 *data, int blocks)
+    */
+   SHA1_VECTOR_ASM     sha1_transform_avx
+
+
 Structure, union, and enumeration documentation
 -----------------------------------------------
 
diff --git a/scripts/kernel-doc b/scripts/kernel-doc
index 54b0893cae66..e23591d3c78c 100755
--- a/scripts/kernel-doc
+++ b/scripts/kernel-doc
@@ -174,6 +174,7 @@ my %nosymbol_table = ();
 my $declaration_start_line;
 my ($type, $declaration_name, $return_type);
 my ($newsection, $newcontents, $prototype, $brcount, %source_map);
+my %asmprototypes;
 
 if (defined($ENV{'KBUILD_VERBOSE'})) {
 	$verbose = "$ENV{'KBUILD_VERBOSE'}";
@@ -248,7 +249,7 @@ my $doc_decl = $doc_com . '(\w+)';
 # while trying to not match literal block starts like "example::"
 #
 my $doc_sect = $doc_com .
-    '\s*(\@[.\w]+|\@\.\.\.|description|context|returns?|notes?|examples?)\s*:([^:].*)?$';
+    '\s*(\@[.\w]+|\@\.\.\.|description|context|returns?|prototype|notes?|examples?)\s*:([^:].*)?$';
 my $doc_content = $doc_com_body . '(.*)';
 my $doc_block = $doc_com . 'DOC:\s*(.*)?';
 my $doc_inline_start = '^\s*/\*\*\s*$';
@@ -278,6 +279,7 @@ my $section_intro = "Introduction";
 my $section = $section_default;
 my $section_context = "Context";
 my $section_return = "Return";
+my $section_asmprototype = "Prototype";
 
 my $undescribed = "-- undescribed --";
 
@@ -469,6 +471,13 @@ sub dump_section {
             $new_start_line = 0;
 	}
     }
+
+    if ($name eq $section_asmprototype) {
+        # extract the function name for future matching to SYM.*FUNC_START.*(name)
+        # since that doesn't include arguments like a C function call
+        my ($func) = ($contents =~ /^.*\s+(\S+)\(/);
+	$asmprototypes{$func} = $contents;
+    }
 }
 
 ##
@@ -1865,9 +1874,32 @@ sub syscall_munge() {
 sub process_proto_function($$) {
     my $x = shift;
     my $file = shift;
+    my $funcname;
 
     $x =~ s@\/\/.*$@@gos; # strip C99-style comments to end of line
 
+    # support asm functions declared with one of these starting in
+    # the first column:
+    #     SYM_FUNC_START(name)
+    #     SYM_FUNC_START_LOCAL(name)
+    #     SYM_FUNC_START_WEAK(name)
+    #     SYM_TYPED_FUNC_START(name)
+    # or for nested macros:
+    #     SOMESTRING<whitespace>name
+    if ($file =~ /\.S$/) {
+        if ($x =~ /^SYM.*FUNC_START/) {
+	    ($funcname) = ($x =~ /^SYM.*FUNC_START.*\((.*)\)/);
+        } elsif ($x =~ /^[A-Za-z0-9_]+\s+[A-Za-z0-9_]+/) {
+	    ($funcname) = ($x =~ /^[A-Za-z0-9_]+\s+([A-Za-z0-9_]+)/);
+        }
+    }
+    if (defined $funcname) {
+	$prototype = $asmprototypes{$funcname};
+	dump_function($asmprototypes{$funcname}, $file);
+	reset_state();
+	return;
+    }
+
     if ($x =~ m#\s*/\*\s+MACDOC\s*#io || ($x =~ /^#/ && $x !~ /^#\s*define/)) {
 	# do nothing
     }
@@ -2106,6 +2138,8 @@ sub process_body($$) {
 	    $newsection = $section_default;
 	} elsif ($newsection =~ m/^context$/i) {
 	    $newsection = $section_context;
+	} elsif ($newsection =~ m/^prototype$/i) {
+	    $newsection = $section_asmprototype;
 	} elsif ($newsection =~ m/^returns?$/i) {
 	    $newsection = $section_return;
 	} elsif ($newsection =~ m/^\@return$/) {
@@ -2156,6 +2190,16 @@ sub process_body($$) {
 		$contents = "";
 		$new_start_line = $.;
 		$state = STATE_BODY;
+	    } elsif ($section eq $section_asmprototype) {
+		my ($protoline) = /Prototype:\s+(.+)$/;
+		my ($funcname) = $protoline =~ /Prototype\.*\s+(\S+)\(/;
+
+		$asmprototypes{$funcname} = $protoline;
+		dump_section($file, $section, $contents);
+		$section = $section_default;
+		$contents = "";
+		$new_start_line = $.;
+		$state = STATE_BODY;
 	    } else {
 		if ($section ne $section_default) {
 		    $state = STATE_BODY_WITH_BLANK_LINE;
@@ -2171,7 +2215,7 @@ sub process_body($$) {
 	    $declaration_purpose =~ s/\s+/ /g;
 	} else {
 	    my $cont = $1;
-	    if ($section =~ m/^@/ || $section eq $section_context) {
+	    if ($section =~ m/^@/ || $section eq $section_context || $section eq $section_asmprototype) {
 		if (!defined $leading_space) {
 		    if ($cont =~ m/^(\s+)/) {
 			$leading_space = $1;
@@ -2307,6 +2351,7 @@ sub process_file($) {
 	}
 	# Replace tabs by spaces
         while ($_ =~ s/\t+/' ' x (length($&) * 8 - length($`) % 8)/e) {};
+
 	# Hand this line to the appropriate state handler
 	if ($state == STATE_NORMAL) {
 	    process_normal();
