From nobody Wed Sep 10 23:21:39 2025 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 85581292B54 for ; Wed, 10 Sep 2025 17:00:30 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757523632; cv=none; b=bA7pvL+3/uS/wRojQrI4rEzdYsNZy3Gcgexdz9a/jNYiATAQPVaadiQsirWN2/9PKBErNbkT2M5pd4EekmNH5BmtvTo+pm0utxTKyrJfNQO5k9dXSajU7rhzCDiNSY5CcOE1hw1CFYU+7bQ57HU3iGf4U/ZDqZ5tGxqnB4Rjl7c= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757523632; c=relaxed/simple; bh=SD5ytb/xU/W1ovRiWGgbNzeHsrA6HUBkcwxbK3JbshE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=qv6zB3KtebfVTuOF0JpnQtzuC6aqIyblLSUxkBI6rTBy6jMlqOzOFaldLxQXjWioxGszAvFneZ0yCyOC+z5UO+q4EN9WP0Tg/gbcqO6wNVJsiMptn17sSTMmaVoYvIQrmDyBBqDR3eRWXzNzLs1baWceBLDzbgPUyykqnYiY1yY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=J8xaRbWn; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="J8xaRbWn" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1757523629; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=ii1zp41sejenDdX+Fc0ZqaS5dEwSpPRBnvyGP/EInUw=; b=J8xaRbWn0coV1J8szVt8HbWrgMrsz7sYHgn/lg6/9irRRGbBFMP7JXo+Nhvuu1f9NVztvf vwjDISnkV0zrEKyLugytAHiD7E9CdUWPLAi68PqIrwM1teZGHEJTvGQjuyJvZCIed0VRqM +6z1SpZp7TOkSF8Aog8Aw9kKi1wUyaY= Received: from mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-38-CvBpK9KkO1SOHH-ct7Qx8w-1; Wed, 10 Sep 2025 13:00:23 -0400 X-MC-Unique: CvBpK9KkO1SOHH-ct7Qx8w-1 X-Mimecast-MFC-AGG-ID: CvBpK9KkO1SOHH-ct7Qx8w_1757523621 Received: from mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.17]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id D3DF9195419F; Wed, 10 Sep 2025 17:00:20 +0000 (UTC) Received: from fedora.redhat.com (unknown [10.44.22.10]) by mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id EBB781956095; Wed, 10 Sep 2025 17:00:14 +0000 (UTC) From: Gabriele Paoloni To: shuah@kernel.org, linux-kselftest@vger.kernel.org, linux-kernel@vger.kernel.org, corbet@lwn.net, linux-doc@vger.kernel.org, gregkh@linuxfoundation.org Cc: linux-mm@kvack.org, safety-architecture@lists.elisa.tech, acarmina@redhat.com, kstewart@linuxfoundation.org, chuckwolber@gmail.com, Gabriele Paoloni Subject: [RFC v2 PATCH 1/3] Documentation: add guidelines for writing testable code specifications Date: Wed, 10 Sep 2025 18:59:58 +0200 Message-ID: <20250910170000.6475-2-gpaoloni@redhat.com> In-Reply-To: <20250910170000.6475-1-gpaoloni@redhat.com> References: <20250910170000.6475-1-gpaoloni@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.0 on 10.30.177.17 The Documentation/doc-guide/kernel-doc.rst chapter describes how to document the code using the kernel-doc format, however it does not specify the criteria to be followed for writing testable specifications; i.e. specifications that can be used to for the semantic description of low level requirements. This patch adds a guideline that defines criteria to formally describe developers=E2=80=99 intent at the function and subfunction level in the form of testable expectations. Signed-off-by: Gabriele Paoloni Signed-off-by: Chuck Wolber Signed-off-by: Kate Stewart --- .../doc-guide/code-specifications.rst | 208 ++++++++++++++++++ Documentation/doc-guide/index.rst | 1 + 2 files changed, 209 insertions(+) create mode 100644 Documentation/doc-guide/code-specifications.rst diff --git a/Documentation/doc-guide/code-specifications.rst b/Documentatio= n/doc-guide/code-specifications.rst new file mode 100644 index 000000000000..dee1b4f089e1 --- /dev/null +++ b/Documentation/doc-guide/code-specifications.rst @@ -0,0 +1,208 @@ +.. title:: How-to write testable code specifications + +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D +How-to write testable code specifications +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +Introduction +------------ +The Documentation/doc-guide/kernel-doc.rst chapter describes how to docume= nt the code using the kernel-doc format, however it does not specify the cr= iteria to be followed for writing testable specifications; i.e. specificati= ons that can be used to for the semantic description of low level requireme= nts. + +This chapter defines criteria to formally describe developers=E2=80=99 int= ent at the function and subfunction level in the form of testable expectati= ons. + +A Virtuous Cycle +---------------- +By adding testable specifications at the function or (where relevant) subf= unction level, one enables the creation of a virtuous cycle when testing is= supplemented with open source code coverage tools like llvm-cov or Gcov. + +As a true reflection of developer intent, code specifications inform the c= reation of a pass/fail tests which can then be assessed in conjunction with= code coverage tools. A failing test may indicate broken code or specificat= ions that fail to capture developer intent. A gap in code coverage may indi= cate missing specifications, unintended functionalities, or insufficient te= st procedure. + +High level goals +---------------- +The code specifications: + +1. Should be maintainable together with the code. +2. Should support hierarchical traceability to allow refinement of SW depe= ndencies (i.e. cross reference critical APIs or data structures). +3. Should describe error conditions and success behaviors. +4. Should describe conditions to be met by the user to avoid unspecified o= r unwanted behaviours. +5. Should allow covering both static and dynamic aspects of the code. +6. Should be compatible with Documentation/doc-guide/kernel-doc.rst. +7. Should support the definition of a test plan (i.e. syntax should enforc= e testability as well as the avoidance of untestable specifications, e.g = =E2=80=9Cfunction_xyz() shall not do something=E2=80=9D). + +Format and Syntax +----------------- +Testable code specifications must be written according to the syntax alrea= dy defined in Documentation/doc-guide/kernel-doc.rst with additional rules = that are described below. + +Function name +~~~~~~~~~~~~~ +``* function_name() - Brief description of function.`` + +This field is to be considered informative and is not part of the testable= specifications. + +Input Arguments +~~~~~~~~~~~~~~~ +Input arguments should be specified in a way that better supports the func= tion=E2=80=99s expectations and Assumptions of Use described below. +They must not contradict the function's expectations and the function=E2= =80=99s prototype. For example:: + + * trace_set_clr_event - enable or disable an event + * @system: system name to match (NULL for any system) + * @event: event name to match (NULL for all events, within system) + * @set: 1 to enable, 0 to disable + * + [...] + * + */ + int trace_set_clr_event(const char *system, const char *event, int set) + +Above all the parameters clearly introduce the impact that they have on th= e code specifications. + +However if below we had:: + + * trace_set_clr_event - enable or disable an event + * @system: system name to match (NULL for any system) + * @event: event name to match (NULL for all events, within system) + * @set: true to enable, false to disable \ + [...] + */ + int trace_set_clr_event(const char *system, const char *event, int set) + +In this case @set would be a bad definition since it is defined as an inte= ger and not as a boolean. + +Longer Description +~~~~~~~~~~~~~~~~~~ +The `Longer Description` section is where the large part of testable code = specifications are defined. The section must be organised as follows:: + + * (Summary Description) provides an introduction of the functionalities + * provided by the function and any informal note. This text does not + * represent any testable code specification. + * + * + * Function's expectations: + * [ID1] - [code expectation] + * + * [ID2] - [code expectation] + * + * [...] + * + * [IDn] - [code expectation] + * + * Assumptions of Use: + * [ID1] - [constraint to be met by the caller] + * + * [ID2] - [constraint to be met by the caller] + * + * [IDn] - [constraint to be met by the caller] + * + +When writing the above section the following rules must be followed: + +* No rules apply to the text above ``Function=E2=80=99s expectations``; su= ch a text does not constitute testable specifications and it is just inform= ative; +* Both ``Function=E2=80=99s expectations`` and ``Assumptions of Use`` must= be listed prefixing each of them with an ID that is unique within this ker= nel-doc header. The reason for this is to facilitate cross-referencing and = traceability between tests and code specifications. +* A Function=E2=80=99s expectation is a testable behavior that the functio= n is expected to comply with (i.e. the function is expected to behave as de= fined in the function=E2=80=99s expectation). +* An Assumption of Use is a pre-condition to be met when invoking the fun= ction being documented. +* Testable functional expectations and Assumptions of Use must be construc= ted according the same rules that apply when writing software requirements: + * Statements should include a subject and a verb, together with other = elements necessary to adequately express the information content of the spe= cifications. + * The verbs are required to use the following keywords: + * For mandatory expectations the verb =E2=80=98shall=E2=80=99 is t= o be used; + * For descriptive text that do not constitute a testable expectati= on verbs such as =E2=80=98are=E2=80=99, =E2=80=98is=E2=80=99, =E2=80=98was= =E2=80=99 are to be used; + * Negative expectations must be avoided (e.g. =E2=80=98shall not= =E2=80=99 must be avoided). +* Statements must be constructed according to the following scheme: + + [**Condition**] [**Subject**] [**Verb/Action**] [**Object**] [**Constr= aint of Action**]. + + In this regard [**Condition**] and [**Constraint of Action**] could be= omitted respectively if the [**Action**] being specified must always happe= n or if there are no constraints associated with it. + +Function Context +~~~~~~~~~~~~~~~~ +The function=E2=80=99s context represents an integral part of Function=E2= =80=99s expectations and Assumptions of Use, where these can further specif= y the information contained in this section. + +Without further specifications this section is to be interpreted as per ex= ample below: + +``* Context: Any context.`` + +The function shall execute in any possible context. + +``* Context: Any context. Takes and releases the RCU lock.`` + +The function shall execute in any possible context. +The function shall take and release the RCU lock. + +``* Context: Any context. Expects to be held by caller.`` + +The function shall execute in any possible context. + is assumed to be held before this function is called. + +``* Context: Process context. May sleep if @gfp flags permit.`` + +The function shall execute in process context. +The function shall sleep according to @gfp flags definitions + +``* Context: Process context. Takes and releases .`` + +The function shall execute in process context. +The function shall take and release . + +``* Context: Softirq or process context. Takes and releases , BH-saf= e.`` + +The function shall execute in process or Softirq context. +The function shall take and release . +The function shall safely execute in bottom half contexts. + +``* Context: Interrupt context.`` + +The function shall execute in interrupt context only. + +It is a good practice to further specify the context specifications as par= t of the Function=E2=80=99s expectation (e.g. at which stage a lock is held= and released) + +Return values +~~~~~~~~~~~~~ +Return values must be written as a multiple line list in the following for= mat:: + +* Return: +* * [value-1] - [condition-1] +* * [value-2] - [condition-2] +* * [...] +* * [value-n] - [condition-n] +* * Any value returned by func-1(), func-2(),...,func-n() + +In such a format ``[value-i]`` must be a clearly identified value or range= of values that is compatible with the function prototype (e.g. for a read(= ) file operation, it is ok to define [value-i] as ``the number of bytes suc= cessfully copied to the user space buffer``). + +``[condition-i]`` must be a condition that can be unambiguously traced bac= k to the ``Function=E2=80=99s expectations`` or ``Context`` defined above; = as part of [condition-i] it is possible to refer to dependencies of invoked= functions or of internal SW or HW states. + +``Any value returned by func-1(), func-2(),...,func-n()`` defines a scenar= io where the current function is directly returning the value of an invoked= function dependency. + +Semantic aspects of testable specifications +------------------------------------------- +From a semantic point of view it is important to document the intended or = expected behavior (from a developer or integrator point of view respectivel= y) in consideration of the different design aspects impacting it. + +Such behavior shall be described in a way that makes it possible to define= test cases unambiguously. \ +To this extent it is important to document design elements impacting the e= xpected behavior and the design elements characterizing the expected behavi= or (and sometimes these can physically overlap); such design elements shall= be limited to the scope of the code being documented, that can range from = a single function to multiple ones depending on the complexity of the overa= ll code. + +**Possible elements impacting the expected behavior** of the code being do= cumented are: + +* Input parameters: parameters passed to the API being documented; +* state variables: global and static data (variables or pointers); +* software dependencies: external SW APIs invoked by the code under analys= is; +* Hardware dependencies: HW design elements directly impacting the behavio= r of the code in scope; +* Firmware dependencies: FW design elements that have an impact on the beh= avior of the API being documented (e.g. DTB or ACPI tables, or runtime serv= ices like SCMI and ACPI AML); +* Compile time configuration parameters: configuration parameters parsed w= hen compiling the Kernel Image; +* Runtime configuration parameters (AKA calibration parameters): parameter= s that can be modified at runtime. + +**Design elements characterizing the expected behavior** of the API being = documented that are in scope according to the above mentioned granularity: + +* API return values, including pointer addresses; +* Input pointers: pointers passed as input parameter to the API being docu= mented; +* state variables: global and static data (variable or pointers); +* Hardware design elements (e.g. HW registers). + +**Testability considerations**: the impact of each of the documented =E2= =80=9Cdesign elements impacting the expected behavior=E2=80=9D must be desc= ribed in terms of effect on the =E2=80=9Cdesign element characterizing the = expected behavior=E2=80=9D and, in doing so, it is important to document al= lowed or not allowed ranges of values, corner cases and error conditions; = so that it is possible to define a meaningful test plan according to differ= ent equivalence classes. + +**Scalability and maintainability considerations**: the described expected= behavior must be limited to the scope of the code under analysis so for ex= ample the Software, Firmware and Hardware dependencies shall be described i= n terms of possible impact on the invoking code deferring further details t= o the respective documentation of these. + +When deciding the scope of the code being documented, the scalability and = maintainability goals must be considered; it does not make sense to embed t= he documentation of multiple complex functions within the kernel-doc header= of the top level function as, doing so, would make it harder to review the= code changes against the documented specifications and/or to extend the sp= ecifications to new functionalities being added. + +The end goal is to build a hierarchical, scalable, maintainable documentat= ion. + +**Feasibility considerations**: Only the =E2=80=9Cmeaningful=E2=80=9D and = =E2=80=9Cuseful=E2=80=9D expected behavior, and the design elements impacti= ng it, shall be considered (e.g. a printk() logging some info may be omitte= d). There are two reasons behind this point: + +1. Specifying the expected behaviour of the code should be done, in princi= ple, in a code agnostic way. So it is not about writing a pseudo-code redun= dant implementation, but rather about defining and documenting the develope= r intent and the integrator=E2=80=99s expectations. +2. When the expected behavior is defined before implementing the code, suc= h an activity is done by experts using a level of detail that is more abstr= act than the code itself and they only refer to aspects that are relevant f= or the design expectations. diff --git a/Documentation/doc-guide/index.rst b/Documentation/doc-guide/in= dex.rst index 24d058faa75c..09e459866442 100644 --- a/Documentation/doc-guide/index.rst +++ b/Documentation/doc-guide/index.rst @@ -9,6 +9,7 @@ How to write kernel documentation =20 sphinx kernel-doc + code-specifications parse-headers contributing maintainer-profile --=20 2.48.1 From nobody Wed Sep 10 23:21:39 2025 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id BD83828C2A6 for ; Wed, 10 Sep 2025 17:00:33 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757523635; cv=none; b=dluLBtWnl8DP19MWwpBxxPpqOXF+tgPIcPicb8TGnAtufy9WSkCINITWPWSLG+fEfuW4ViLNMVAldSE+Qvdzk1krQnaUiPFcuc4VIKD7b0mt9B16a+4zGGue0ByLUpe2V1bKKWQWu/KFq8dDrDZspYYeGUsp/Ks8IHJ7V4wQhjo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757523635; c=relaxed/simple; bh=p+NVPNkWx25cSfYoK0FO8C9FqWaXq5V69UlkKtuHWKM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Ij1FU29Dk7QzrkUQBxlgprFBfuDzZpp5yiDQ1TV97YCAABIpcIa39F5zohc8NpGi1JMdZBFuRquAO6OKWco0ACooLTkDeuiUkLNGUfkJ1ZatPS8D2rXYF17hFkRAeW+WfpehaN+2WhI3oM808rtiNJsuJv6ZC4xKzGPSUZewPuk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=XQ+Er6Sh; arc=none smtp.client-ip=170.10.129.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="XQ+Er6Sh" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1757523632; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=UDjic9ICmOZRP08A7LmsmRHImNuEcHgVdZxPz5GGj18=; b=XQ+Er6Sh11EUChp1XCVppvAGQ6EfM6cW/UKzd4kRaazKNNSrpzRdF/XtQd/qjRsHusa2Gk 85QEpnCVZi301UAGPuIGVUFr8i0M04a8UNUPz4Ze2Xs8lhwBFDnXFBYnjZq/1aVuPoRlpp yeFW9O9kh6b1OwFmxZqqcHTQZCxKmvQ= Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-501-FP45MaW2Ng2VZ2jA6U_uyg-1; Wed, 10 Sep 2025 13:00:29 -0400 X-MC-Unique: FP45MaW2Ng2VZ2jA6U_uyg-1 X-Mimecast-MFC-AGG-ID: FP45MaW2Ng2VZ2jA6U_uyg_1757523627 Received: from mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.17]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 78DDB18002C0; Wed, 10 Sep 2025 17:00:27 +0000 (UTC) Received: from fedora.redhat.com (unknown [10.44.22.10]) by mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 748331956095; Wed, 10 Sep 2025 17:00:21 +0000 (UTC) From: Gabriele Paoloni To: shuah@kernel.org, linux-kselftest@vger.kernel.org, linux-kernel@vger.kernel.org, corbet@lwn.net, linux-doc@vger.kernel.org, gregkh@linuxfoundation.org Cc: linux-mm@kvack.org, safety-architecture@lists.elisa.tech, acarmina@redhat.com, kstewart@linuxfoundation.org, chuckwolber@gmail.com, Gabriele Paoloni Subject: [RFC v2 PATCH 2/3] /dev/mem: Add initial documentation of memory_open() and mem_fops Date: Wed, 10 Sep 2025 18:59:59 +0200 Message-ID: <20250910170000.6475-3-gpaoloni@redhat.com> In-Reply-To: <20250910170000.6475-1-gpaoloni@redhat.com> References: <20250910170000.6475-1-gpaoloni@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.0 on 10.30.177.17 Content-Type: text/plain; charset="utf-8" This patch proposes initial kernel-doc documentation for memory_open() and most of the functions in the mem_fops structure. The format used for the specifications follows the guidelines defined in Documentation/doc-guide/code-specifications.rst Signed-off-by: Gabriele Paoloni --- drivers/char/mem.c | 231 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 225 insertions(+), 6 deletions(-) diff --git a/drivers/char/mem.c b/drivers/char/mem.c index 48839958b0b1..e69c164e9465 100644 --- a/drivers/char/mem.c +++ b/drivers/char/mem.c @@ -75,9 +75,54 @@ static inline bool should_stop_iteration(void) return signal_pending(current); } =20 -/* - * This funcion reads the *physical* memory. The f_pos points directly to = the - * memory location. +/** + * read_mem - read from physical memory (/dev/mem). + * @file: struct file associated with /dev/mem. + * @buf: user-space buffer to copy data to. + * @count: number of bytes to read. + * @ppos: pointer to the current file position, representing the physical + * address to read from. + * + * This function checks if the requested physical memory range is valid + * and accessible by the user, then it copies data to the input + * user-space buffer up to the requested number of bytes. + * + * Function's expectations: + * + * 1. This function shall check if the value pointed by ppos exceeds the + * maximum addressable physical address; + * + * 2. This function shall check if the physical address range to be read + * is valid (i.e. it falls within a memory block and if it can be mapped + * to the kernel address space); + * + * 3. For each memory page falling in the requested physical range + * [ppos, ppos + count - 1]: + * 3.1. this function shall check if user space access is allowed (if + * config STRICT_DEVMEM is not set, access is always granted); + * + * 3.2. if access is allowed, the memory content from the page range fal= ling + * within the requested physical range shall be copied to the user = space + * buffer; + * + * 3.3. zeros shall be copied to the user space buffer (for the page ran= ge + * falling within the requested physical range): + * 3.3.1. if access to the memory page is restricted or, + * 3.2.2. if the current page is page 0 on HW architectures where page= 0 is + * not mapped. + * + * 4. The file position '*ppos' shall be advanced by the number of bytes + * successfully copied to user space (including zeros). + * + * Context: process context. + * + * Return: + * * the number of bytes copied to user on success + * * %-EFAULT - the requested address range is not valid or a fault happen= ed + * when copying to user-space (i.e. copy_from_kernel_nofault() failed) + * * %-EPERM - access to any of the required physical pages is not allowed + * * %-ENOMEM - out of memory error for auxiliary kernel buffers supporting + * the operation of copying content from the physical pages */ static ssize_t read_mem(struct file *file, char __user *buf, size_t count, loff_t *ppos) @@ -166,6 +211,54 @@ static ssize_t read_mem(struct file *file, char __user= *buf, return err; } =20 +/** + * write_mem - write to physical memory (/dev/mem). + * @file: struct file associated with /dev/mem. + * @buf: user-space buffer containing the data to write. + * @count: number of bytes to write. + * @ppos: pointer to the current file position, representing the physical + * address to write to. + * + * This function checks if the target physical memory range is valid + * and accessible by the user, then it writes data from the input + * user-space buffer up to the requested number of bytes. + * + * Function's expectations: + * 1. This function shall check if the value pointed by ppos exceeds the + * maximum addressable physical address; + * + * 2. This function shall check if the physical address range to be written + * is valid (i.e. it falls within a memory block and if it can be mapped + * to the kernel address space); + * + * 3. For each memory page falling in the physical range to be written + * [ppos, ppos + count - 1]: + * 3.1. this function shall check if user space access is allowed (if + * config STRICT_DEVMEM is not set, access is always granted); + * + * 3.2. the content from the user space buffer shall be copied to the pa= ge + * range falling within the physical range to be written if access = is + * allowed; + * + * 3.3. the data to be copied from the user space buffer (for the page r= ange + * falling within the range to be written) shall be skipped: + * 3.3.1. if access to the memory page is restricted or, + * 3.3.2. if the current page is page 0 on HW architectures where page= 0 + * is not mapped. + * + * 4. The file position '*ppos' shall be advanced by the number of bytes + * successfully copied from user space (including skipped bytes). + * + * Context: process context. + * + * Return: + * * the number of bytes copied from user-space on success + * * %-EFBIG - the value pointed by ppos exceeds the maximum addressable + * physical address + * * %-EFAULT - the physical address range is not valid or no bytes could + * be copied from user-space + * * %-EPERM - access to any of the required pages is not allowed + */ static ssize_t write_mem(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { @@ -322,6 +415,42 @@ static const struct vm_operations_struct mmap_mem_ops = =3D { #endif }; =20 +/** + * mmap_mem - map physical memory into user space (/dev/mem). + * @file: file structure for the device. + * @vma: virtual memory area structure describing the user mapping. + * + * This function checks if the requested physical memory range is valid + * and accessible by the user, then it maps the physical memory range to + * user-mode address space. + * + * Function's expectations: + * 1. This function shall check if the requested physical address range to= be + * mapped fits within the maximum addressable physical range; + * + * 2. This function shall check if the requested physical range correspon= ds to + * a valid physical range and if access is allowed on it (if config STR= ICT_DEVMEM + * is not set, access is always allowed); + * + * 3. This function shall check if the input virtual memory area can be us= ed for + * a private mapping (always OK if there is an MMU); + * + * 4. This function shall set the virtual memory area operations to + * &mmap_mem_ops; + * + * 5. This function shall establish a mapping between the user-space + * virtual memory area described by vma and the physical memory + * range specified by vma->vm_pgoff and size; + * + * Context: process context. + * + * Return: + * * 0 on success + * * %-EAGAIN - invalid or unsupported mapping requested (remap_pfn_range() + * fails) + * * %-EINVAL - requested physical range to be mapped is not valid + * * %-EPERM - no permission to access the requested physical range + */ static int mmap_mem(struct file *file, struct vm_area_struct *vma) { size_t size =3D vma->vm_end - vma->vm_start; @@ -550,13 +679,47 @@ static loff_t null_lseek(struct file *file, loff_t of= fset, int orig) return file->f_pos =3D 0; } =20 -/* +/** + * memory_lseek - change the file position. + * @file: file structure for the device. + * @offset: file offset to seek to. + * @orig: where to start seeking from (see whence in the llseek manpage). + * + * This function changes the file position according to the input offset + * and orig parameters. + * + * Function's expectations: + * 1. This function shall lock the semaphore of the inode corresponding to= the + * input file before any operation and unlock it before returning. + * + * 2. This function shall check the orig value and accordingly: + * 2.1. if it is equal to SEEK_CUR, the current file position shall be + * incremented by the input offset; + * 2.2. if it is equal to SEEK_SET, the current file position shall be + * set to the input offset value; + * 2.3. any other value shall result in an error condition. + * + * 3. Before writing the current file position, the new position value + * shall be checked to not overlap with Linux ERRNO values. + * + * Assumptions of Use: + * 1. the input file pointer is expected to be valid. + * + * Notes: * The memory devices use the full 32/64 bits of the offset, and so we can= not * check against negative addresses: they are ok. The return value is weir= d, * though, in that case (0). * - * also note that seeking relative to the "end of file" isn't supported: - * it has no meaning, so it returns -EINVAL. + * Also note that seeking relative to the "end of file" isn't supported: + * it has no meaning, so passing orig equal to SEEK_END returns -EINVAL. + * + * Context: process context, locks/unlocks inode->i_rwsem + * + * Return: + * * the new file position on success + * * %-EOVERFLOW - the new position value equals or exceeds + * (unsigned long long) -MAX_ERRNO + * * %-EINVAL - the orig parameter is invalid */ static loff_t memory_lseek(struct file *file, loff_t offset, int orig) { @@ -584,6 +747,35 @@ static loff_t memory_lseek(struct file *file, loff_t o= ffset, int orig) return ret; } =20 +/** + * open_port - open the I/O port device (/dev/port). + * @inode: inode of the device file. + * @filp: file structure for the device. + * + * This function checks if the caller can access the port device and sets + * the f_mapping pointer of filp to the i_mapping pointer of inode. + * + * Function's expectations: + * 1. This function shall check if the caller has sufficient capabilities = to + * perform raw I/O access; + * + * 2. This function shall check if the kernel is locked down with the + * &LOCKDOWN_DEV_MEM restriction; + * + * 3. If the input inode corresponds to /dev/mem, the f_mapping pointer + * of the input file structure shall be set to the i_mapping pointer + * of the input inode; + * + * Assumptions of Use: + * 1. The input inode and filp are expected to be valid. + * + * Context: process context. + * + * Return: + * * 0 on success + * * %-EPERM - caller lacks the required capability (CAP_SYS_RAWIO) + * * any error returned by securty_locked_down() + */ static int open_port(struct inode *inode, struct file *filp) { int rc; @@ -691,6 +883,33 @@ static const struct memdev { #endif }; =20 +/** + * memory_open - set the filp f_op to the memory device fops and invoke op= en(). + * @inode: inode of the device file. + * @filp: file structure for the device. + * + * Function's expectations: + * 1. This function shall retrieve the minor number associated with the in= put + * inode and the memory device corresponding to such minor number; + * + * 2. The file operations pointer shall be set to the memory device file o= perations; + * + * 3. The file mode member of the input filp shall be OR'd with the device= mode; + * + * 4. The memory device open() file operation shall be invoked. + * + * Assumptions of Use: + * 1. The input inode and filp are expected to be non-NULL. + * + * Context: process context. + * + * Return: + * * 0 on success + * * %-ENXIO - the minor number corresponding to the input inode cannot be + * associated with any device or the corresponding device has a NULL fops + * pointer + * * any error returned by the device specific open function pointer + */ static int memory_open(struct inode *inode, struct file *filp) { int minor; --=20 2.48.1 From nobody Wed Sep 10 23:21:39 2025 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 07F4929C327 for ; Wed, 10 Sep 2025 17:00:40 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757523645; cv=none; b=sxX4iKcTe5nniQQgvxAZIekxrx+imMw8FOOJozzoSbzZhakdTdnOHV7ngZMjzFROd4PIZKYk2apBrX87rHQv/XGb7PV0+sNXvcYNr0Xy8Bp1gWGcpOlPDN5MwkeOlQnKz7G19+mhnM+AIS+0Aife8LNWnY8/NXPVk+1mOqN1EXc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1757523645; c=relaxed/simple; bh=0sIXJQSp7LZzik7Xp3UXK+5svlcx1Nb8fZIumzgVzM4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=PexivSUdzsyTqvlDQLOPl823oDG1U6m35TqSQ+sGY+1eJsjer0eJ7Jl1N0Hmj4jUhf4wPo/j/3IRwroh58L43g+9/thayKHhqOUSGs2WS4HQsSjUG7cGOCipfZ44EXgM0mw4P23HZydld9CETA+ZUsYfpm7URt7rfVC7/JKvHrQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=h6r+g7rz; arc=none smtp.client-ip=170.10.129.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="h6r+g7rz" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1757523640; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=OgXJ8j/p1zT2LHdjlIBg+mBx0F8XKwBvEhpf4celif0=; b=h6r+g7rz6Ub7YvvZf/qxrE4Xp99yuNliV5rCRAMsbgshYyo3norlmM069Ke3XJH3h9zGGM M9DNZ/YbHhjPTRhYNfULg/5kjNUyreQgaCEP+raHXu/PPZTgYVzK+i8Ef0EUojsd1Yxxgn 0dw1Yl2ifcM48rtpokQ36ss/UwWUXM8= Received: from mx-prod-mc-04.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-447-8QG3kGF3OyquddQp6p04rA-1; Wed, 10 Sep 2025 13:00:36 -0400 X-MC-Unique: 8QG3kGF3OyquddQp6p04rA-1 X-Mimecast-MFC-AGG-ID: 8QG3kGF3OyquddQp6p04rA_1757523634 Received: from mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.17]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-04.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 3E2B719560B0; Wed, 10 Sep 2025 17:00:34 +0000 (UTC) Received: from fedora.redhat.com (unknown [10.44.22.10]) by mx-prod-int-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 380C81956095; Wed, 10 Sep 2025 17:00:27 +0000 (UTC) From: Gabriele Paoloni To: shuah@kernel.org, linux-kselftest@vger.kernel.org, linux-kernel@vger.kernel.org, corbet@lwn.net, linux-doc@vger.kernel.org, gregkh@linuxfoundation.org Cc: linux-mm@kvack.org, safety-architecture@lists.elisa.tech, acarmina@redhat.com, kstewart@linuxfoundation.org, chuckwolber@gmail.com Subject: [RFC v2 PATCH 3/3] selftests/devmem: initial testset Date: Wed, 10 Sep 2025 19:00:00 +0200 Message-ID: <20250910170000.6475-4-gpaoloni@redhat.com> In-Reply-To: <20250910170000.6475-1-gpaoloni@redhat.com> References: <20250910170000.6475-1-gpaoloni@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.0 on 10.30.177.17 Content-Type: text/plain; charset="utf-8" From: Alessandro Carminati This patch introduces a new series of tests for devmem. Test cases are mapped against the tested Function's expectations defined in /drivers/char/mem.c. Signed-off-by: Alessandro Carminati --- tools/testing/selftests/Makefile | 1 + tools/testing/selftests/devmem/Makefile | 13 + tools/testing/selftests/devmem/debug.c | 25 + tools/testing/selftests/devmem/debug.h | 14 + tools/testing/selftests/devmem/devmem.c | 200 ++++++++ tools/testing/selftests/devmem/ram_map.c | 250 ++++++++++ tools/testing/selftests/devmem/ram_map.h | 38 ++ tools/testing/selftests/devmem/secret.c | 46 ++ tools/testing/selftests/devmem/secret.h | 13 + tools/testing/selftests/devmem/tests.c | 569 +++++++++++++++++++++++ tools/testing/selftests/devmem/tests.h | 45 ++ tools/testing/selftests/devmem/utils.c | 379 +++++++++++++++ tools/testing/selftests/devmem/utils.h | 119 +++++ 13 files changed, 1712 insertions(+) create mode 100644 tools/testing/selftests/devmem/Makefile create mode 100644 tools/testing/selftests/devmem/debug.c create mode 100644 tools/testing/selftests/devmem/debug.h create mode 100644 tools/testing/selftests/devmem/devmem.c create mode 100644 tools/testing/selftests/devmem/ram_map.c create mode 100644 tools/testing/selftests/devmem/ram_map.h create mode 100644 tools/testing/selftests/devmem/secret.c create mode 100644 tools/testing/selftests/devmem/secret.h create mode 100644 tools/testing/selftests/devmem/tests.c create mode 100644 tools/testing/selftests/devmem/tests.h create mode 100644 tools/testing/selftests/devmem/utils.c create mode 100644 tools/testing/selftests/devmem/utils.h diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Mak= efile index 030da61dbff3..55d228572e37 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -16,6 +16,7 @@ TARGETS +=3D cpu-hotplug TARGETS +=3D damon TARGETS +=3D devices/error_logs TARGETS +=3D devices/probe +TARGETS +=3D devmem/devmem TARGETS +=3D dmabuf-heaps TARGETS +=3D drivers/dma-buf TARGETS +=3D drivers/ntsync diff --git a/tools/testing/selftests/devmem/Makefile b/tools/testing/selfte= sts/devmem/Makefile new file mode 100644 index 000000000000..8aca8cbe2957 --- /dev/null +++ b/tools/testing/selftests/devmem/Makefile @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0 +# Kselftest Makefile for devmem test + +CFLAGS +=3D -Wall -O2 + +TEST_GEN_PROGS_EXTENDED :=3D devmem + +$(OUTPUT)/devmem: devmem.c $(OUTPUT)/ram_map.o $(OUTPUT)/secret.o $(OUTPU= T)/tests.o $(OUTPUT)/utils.o $(OUTPUT)/debug.o + $(CC) $^ -o $@ $(CFLAGS) + +EXTRA_CLEAN +=3D $(OUTPUT)/ram_map.o $(OUTPUT)/secret.o $(OUTPUT)/tests.o= $(OUTPUT)/utils.o $(OUTPUT)/debug.o + +include ../lib.mk diff --git a/tools/testing/selftests/devmem/debug.c b/tools/testing/selftes= ts/devmem/debug.c new file mode 100644 index 000000000000..db88a760414d --- /dev/null +++ b/tools/testing/selftests/devmem/debug.c @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * devmem test debug.c + * + * Copyright (C) 2025 Red Hat, Inc. All Rights Reserved. + * Written by Alessandro Carminati (acarmina@redhat.com) + */ + +#include +#include + +#define DEBUG_FLAG 0 +int pdebug =3D DEBUG_FLAG; + +void deb_printf(const char *fmt, ...) +{ + va_list args; + + if (pdebug) { + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); + } +} + diff --git a/tools/testing/selftests/devmem/debug.h b/tools/testing/selftes= ts/devmem/debug.h new file mode 100644 index 000000000000..323f44b94aaa --- /dev/null +++ b/tools/testing/selftests/devmem/debug.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * devmem test debug.h + * + * Copyright (C) 2025 Red Hat, Inc. All Rights Reserved. + * Written by Alessandro Carminati (acarmina@redhat.com) + */ + +#ifndef DEBUG_H +#define DEBUG_H +extern int pdebug; +void deb_printf(const char *fmt, ...); +#endif + diff --git a/tools/testing/selftests/devmem/devmem.c b/tools/testing/selfte= sts/devmem/devmem.c new file mode 100644 index 000000000000..1b3fa5a46f67 --- /dev/null +++ b/tools/testing/selftests/devmem/devmem.c @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* devmem test devmem.c + * + * Copyright (C) 2025 Red Hat, Inc. All Rights Reserved. + * Written by Alessandro Carminati (acarmina@redhat.com) + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" +#include "secret.h" +#include "debug.h" +#include "ram_map.h" +#include "tests.h" +#include "debug.h" +#include "../kselftest.h" + +struct char_mem_test test_set[] =3D { +{ + "test_devmem_access", + &test_devmem_access, + "Test whether /dev/mem is accessible - memory_open FE_1, FE_2, FE_4", + F_ARCH_ALL|F_BITS_ALL|F_MISC_FATAL|F_MISC_INIT_PRV +}, +{ "test_open_devnum", + &test_open_devnum, + "Test open /dev/mem provides the correct min, maj - memory_open - FE_3", + F_ARCH_ALL|F_BITS_ALL|F_MISC_INIT_REQ}, +{ + "test_strict_devmem", + &test_strict_devmem, + "Test Strict Devmem enabled - Dependency", + F_ARCH_ALL|F_BITS_ALL|F_MISC_STRICT_DEVMEM_PRV|F_MISC_DONT_CARE +}, +{ + "test_read_at_addr_32bit_ge", + &test_read_at_addr_32bit_ge, + "Test read 64bit ppos vs 32 bit addr - read_mem - FE_1", + F_ARCH_ALL|F_BITS_B32|F_MISC_INIT_REQ +}, +{ + "test_read_outside_linear_map", + &test_read_outside_linear_map, + "Test read outside linear map - read_mem - FE_2", + F_ARCH_ALL|F_BITS_B32|F_MISC_INIT_REQ +}, +{ + "test_read_secret_area", + &test_read_secret_area, + "Test read memfd_secret area can not being accessed - read_mem - FE_4", + F_ARCH_ALL|F_BITS_ALL|F_MISC_INIT_REQ +}, +{ + "test_read_allowed_area", + &test_read_allowed_area, + "test read allowed area - read_mem - FE_5", + F_ARCH_ALL|F_BITS_ALL|F_MISC_INIT_REQ +}, +{ + "test_read_allowed_area_ppos_advance", + &test_read_allowed_area_ppos_advance, + "test read allowed area increments ppos - read_mem - FE_3", + F_ARCH_ALL|F_BITS_ALL|F_MISC_INIT_REQ +}, +{ + "test_read_restricted_area", + &test_read_restricted_area, + "test read restricted returns zeros - read_mem - FE_6", + F_ARCH_X86|F_BITS_ALL|F_MISC_INIT_REQ|F_MISC_STRICT_DEVMEM_REQ +}, +{ + "test_write_outside_area", + &test_write_outside_area, + "test write outside - write_mem - FE_2", + F_ARCH_ALL|F_BITS_ALL|F_MISC_INIT_REQ|F_MISC_WARN_ON_FAILURE +}, +{ + "test_seek_seek_set", + &test_seek_seek_set, + "test seek funcction SEEK_SET - memory_lseek - FE_4", + F_ARCH_ALL|F_BITS_ALL|F_MISC_INIT_REQ +}, +{ + "test_seek_seek_cur", + &test_seek_seek_cur, + "test seek function SEEK_CUR - memory_lseek - FE_3", + F_ARCH_ALL|F_BITS_ALL|F_MISC_INIT_REQ +}, +{ + "test_seek_seek_other", + &test_seek_seek_other, + "test seek function SEEK_END other - memory_lseek - FE_5", + F_ARCH_ALL|F_BITS_ALL|F_MISC_INIT_REQ +}, +}; + +int main(int argc, char *argv[]) +{ + int tests_skipped =3D 0; + int tests_failed =3D 0; + int tests_passed =3D 0; + int i, tmp_res; + struct test_context t; + char *str_res, *str_warn; + struct char_mem_test *current; + + t.srcbuf =3D malloc_pb(BOUNCE_BUF_SIZE); + t.dstbuf =3D malloc_pb(BOUNCE_BUF_SIZE); + if (!t.srcbuf || !t.dstbuf) { + printf("can't allocate buffers!\n"); + exit(-1); + } + // seet verbose flag from cmdline + t.verbose =3D false; + if ((argc >=3D 2) && (!strcmp(argv[1], "-v"))) { + t.verbose =3D true; + pdebug =3D 1; + } + + t.map =3D parse_iomem(); + if (!t.map) + goto exit; + + if (t.verbose) { + report_physical_memory(t.map); + dump_ram_map(t.map); + } + + for (i =3D 0; i < ARRAY_SIZE(test_set); i++) { + str_warn =3D NO_WARN_STR; + current =3D test_set + i; + tmp_res =3D test_needed(&t, current); + switch (tmp_res) { + case TEST_INCOHERENT: + deb_printf("Incoherent sequence Detected\n"); + exit(-1); + break; + case TEST_ALLOWED: + deb_printf("allowed sequence Detected\n"); + str_res =3D ""; + printf("%s - (%s) ", current->name, current->descr); + tmp_res =3D current->fn(&t); + switch (tmp_res) { + case FAIL: + str_res =3D DC_STR; + if (!(current->flags & F_MISC_DONT_CARE)) { + str_res =3D KO_STR; + tests_failed++; + } + break; + case SKIPPED: + tests_skipped++; + str_res =3D SKP_STR; + if (current->flags & F_MISC_WARN_ON_FAILURE) + str_warn =3D WARN_STR; + break; + case PASS: + str_res =3D DC_STR; + if (!(current->flags & F_MISC_DONT_CARE)) { + tests_passed++; + str_res =3D OK_STR; + } + if (current->flags & F_MISC_WARN_ON_SUCCESS) + str_warn =3D WARN_STR; + break; + default: + tests_failed++; + printf("corrupted data\n"); + exit(-1); + } + ksft_print_msg("%s %s\n", str_res, str_warn); + if ((tmp_res =3D=3D FAIL) && + (current->flags & F_MISC_FATAL)) { + printf("fatal test failed end the chain\n"); + goto cleanup; + } + case TEST_DENIED: + deb_printf("denied sequence Detected\n"); + } + } + +cleanup: + close(t.fd); + free_ram_map(t.map); + free_pb(t.srcbuf); + free_pb(t.dstbuf); +exit: + printf("Run tests =3D %d (passed=3D%d, skipped=3D%d failed=3D%d)\n", + tests_skipped+tests_failed+tests_passed, tests_passed, + tests_skipped, tests_failed); + return tests_skipped+tests_failed; +} diff --git a/tools/testing/selftests/devmem/ram_map.c b/tools/testing/selft= ests/devmem/ram_map.c new file mode 100644 index 000000000000..cc8855052b75 --- /dev/null +++ b/tools/testing/selftests/devmem/ram_map.c @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* devmem test ram_map.c + * + * Copyright (C) 2025 Red Hat, Inc. All Rights Reserved. + * Written by Alessandro Carminati (acarmina@redhat.com) + */ + +#include +#include +#include +#include +#include +#include +#include "ram_map.h" +#include "utils.h" +#include "debug.h" + +static int calculate_bits(uint64_t max_addr) +{ + uint64_t value =3D max_addr + 1; + int bits =3D 0; + + while (value > 0) { + value >>=3D 1; + bits++; + } + return bits; +} + +uint64_t get_highest_ram_addr(const struct ram_map *map) +{ + if (!map || map->count =3D=3D 0) + return 0; + return map->regions[map->count - 1].end; +} + +static int fill_iomem_regions(FILE *fp, struct ram_map *map) +{ + char line[512]; + uint64_t start, end; + char name[256]; + size_t idx =3D 0; + + while (fgets(line, sizeof(line), fp)) { + if (sscanf(line, "%" SCNx64 "-%" SCNx64 " : %255[^\n]", + &start, &end, name) =3D=3D 3) { + map->regions[idx].start =3D start; + map->regions[idx].end =3D end; + map->regions[idx].name =3D strdup(name); + if (!map->regions[idx].name) { + perror("strdup"); + return -1; + } + idx++; + } + } + return 0; +} + +static size_t count_iomem_regions(FILE *fp) +{ + char line[512]; + size_t count =3D 0; + uint64_t start, end; + char name[256]; + + rewind(fp); + while (fgets(line, sizeof(line), fp)) { + if (sscanf(line, "%" SCNx64 "-%" SCNx64 " : %255[^\n]", + &start, &end, name) =3D=3D 3) { + count++; + } + } + rewind(fp); + return count; +} + +struct ram_map *parse_iomem(void) +{ + FILE *fp =3D fopen("/proc/iomem", "r"); + + if (!fp) { + perror("fopen /proc/iomem"); + return NULL; + } + + size_t count =3D count_iomem_regions(fp); + + if (count =3D=3D 0) { + fprintf(stderr, "No parsable regions found in /proc/iomem.\n"); + fclose(fp); + return NULL; + } + + struct ram_map *map =3D calloc(1, sizeof(*map)); + + if (!map) { + perror("calloc map"); + fclose(fp); + return NULL; + } + + map->regions =3D calloc(count, sizeof(*map->regions)); + if (!map->regions) { + perror("calloc regions"); + free(map); + fclose(fp); + return NULL; + } + map->count =3D count; + + if (fill_iomem_regions(fp, map) < 0) { + fclose(fp); + return NULL; + } + + fclose(fp); + return map; +} + +void free_ram_map(struct ram_map *map) +{ + if (!map) + return; + + for (size_t i =3D 0; i < map->count; i++) + free(map->regions[i].name); + + free(map->regions); + free(map); +} + +uint64_t find_last_linear_byte(int fd, uint64_t low_start, uint64_t max_ad= dr) +{ + uint64_t low =3D low_start + SAFE_OFFSET; + uint64_t high =3D max_addr; + uint64_t last_good =3D 0; + + while (low <=3D high) { + uint64_t mid =3D low + (high - low) / 2; + int ret =3D try_read_dev_mem(fd, mid, 0, NULL); + + if (ret > 0) { + last_good =3D mid; + low =3D mid + 1; + } else if (ret =3D=3D -EFAULT) { + if (mid =3D=3D 0) + break; + high =3D mid - 1; + } else { + deb_printf("Unexpected error at 0x%llx: %d\n", + (unsigned long long)mid, -ret); + break; + } + } + return last_good; +} + +void dump_ram_map(const struct ram_map *map) +{ + printf("Parsed RAM map (%zu regions):\n", map->count); + + for (size_t i =3D 0; i < map->count; i++) { + printf(" %016" SCNx64 "-%016" SCNx64 " : %s\n", + map->regions[i].start, + map->regions[i].end, + map->regions[i].name); + } +} + +void report_physical_memory(const struct ram_map *map) +{ + uint64_t highest_addr =3D get_highest_ram_addr(map); + + if (highest_addr =3D=3D 0) { + printf("No System RAM regions detected!\n"); + return; + } + + int bits =3D calculate_bits(highest_addr); + + printf("Highest physical RAM address: 0x%llx\n", + (unsigned long long)highest_addr); + printf("Physical address width (installed RAM): %d bits\n", bits); +} + +uint64_t find_high_system_ram_addr(const struct ram_map *map) +{ + for (size_t i =3D 0; i < map->count; i++) { + if (strstr(map->regions[i].name, "System RAM") && + map->regions[i].start >=3D LOW_MEM_LIMIT) { + return map->regions[i].start; + } + } + return 0; +} + +uint64_t pick_restricted_address(const struct ram_map *map) +{ + if (!map || !map->regions || map->count =3D=3D 0) + return 0; + + for (size_t i =3D 0; i < map->count; i++) { + if ((!strcmp("System RAM", map->regions[i].name)) && + (map->regions[i].start < LEGACY_MEM_START)) { + uint64_t start =3D map->regions[i].start; + uint64_t end =3D map->regions[i].end; + + if (end > start) + return start + (end - start) / 2; + } + } + + return 0; +} + +uint64_t pick_outside_address(const struct ram_map *map) +{ + uint64_t max_addr =3D 0; + + if (!map || !map->regions || map->count =3D=3D 0) + return 0; + + for (size_t i =3D 0; i < map->count; i++) { + if (max_addr < map->regions[i].end) + max_addr =3D map->regions[i].end; + } + + return max_addr + 0x1000; +} + +uint64_t pick_valid_ram_address(const struct ram_map *map) +{ + uint64_t best_low =3D 0, best_size =3D 0; + + if (!map || !map->regions || map->count =3D=3D 0) + return 0; + + for (size_t i =3D 0; i < map->count; i++) { + if (!strcmp("System RAM", map->regions[i].name)) { + if (best_size < map->regions[i].end - + map->regions[i].start) { + best_low =3D map->regions[i].end; + best_size =3D map->regions[i].end - + map->regions[i].start; + } + } + } + return best_low + (best_size / 2); +} diff --git a/tools/testing/selftests/devmem/ram_map.h b/tools/testing/selft= ests/devmem/ram_map.h new file mode 100644 index 000000000000..8b1bd976b0b9 --- /dev/null +++ b/tools/testing/selftests/devmem/ram_map.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* devmem test ram_map.h + * + * Copyright (C) 2025 Red Hat, Inc. All Rights Reserved. + * Written by Alessandro Carminati (acarmina@redhat.com) + */ + +#ifndef RAM_MAP_H +#define RAM_MAP_H + +#define _GNU_SOURCE +#define SAFE_OFFSET (512ULL * 1024ULL) +#define LOW_MEM_LIMIT 0x100000ULL +#define LEGACY_MEM_START 0x10000 + +struct ram_region { + uint64_t start; + uint64_t end; + char *name; +}; + +struct ram_map { + struct ram_region *regions; + size_t count; +}; + +uint64_t get_highest_ram_addr(const struct ram_map *map); +struct ram_map *parse_iomem(void); +void free_ram_map(struct ram_map *map); +uint64_t find_last_linear_byte(int fd, uint64_t low_start, uint64_t max_ad= dr); +void dump_ram_map(const struct ram_map *map); +void report_physical_memory(const struct ram_map *map); +uint64_t find_high_system_ram_addr(const struct ram_map *map); +uint64_t pick_restricted_address(const struct ram_map *map); +uint64_t pick_outside_address(const struct ram_map *map); +uint64_t pick_valid_ram_address(const struct ram_map *map); + +#endif diff --git a/tools/testing/selftests/devmem/secret.c b/tools/testing/selfte= sts/devmem/secret.c new file mode 100644 index 000000000000..164f58947af5 --- /dev/null +++ b/tools/testing/selftests/devmem/secret.c @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* devmem test secret.c + * + * Copyright (C) 2025 Red Hat, Inc. All Rights Reserved. + * Written by Alessandro Carminati (acarmina@redhat.com) + */ + +#include +#include +#include + + +static int memfd_secret(unsigned int flags) +{ + return syscall(SYS_memfd_secret, flags); +} + +void *secret_alloc(size_t size) +{ + int fd =3D -1; + void *m; + void *result =3D NULL; + + fd =3D memfd_secret(0); + if (fd < 0) + goto out; + + if (ftruncate(fd, size) < 0) + goto out; + + m =3D mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (m =3D=3D MAP_FAILED) + goto out; + + result =3D m; + +out: + if (fd >=3D 0) + close(fd); + return result; +} + +void secret_free(void *p, size_t size) +{ + munmap(p, size); +} diff --git a/tools/testing/selftests/devmem/secret.h b/tools/testing/selfte= sts/devmem/secret.h new file mode 100644 index 000000000000..07d263fc05e3 --- /dev/null +++ b/tools/testing/selftests/devmem/secret.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* devmem test secret.h + * + * Copyright (C) 2025 Red Hat, Inc. All Rights Reserved. + * Written by Alessandro Carminati (acarmina@redhat.com) + */ + +#ifndef SECRET_H +#define SECRET_H + +void *secret_alloc(size_t size); +void secret_free(void *p, size_t size); +#endif diff --git a/tools/testing/selftests/devmem/tests.c b/tools/testing/selftes= ts/devmem/tests.c new file mode 100644 index 000000000000..58ad673c438f --- /dev/null +++ b/tools/testing/selftests/devmem/tests.c @@ -0,0 +1,569 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* devmem test tests.c + * + * Copyright (C) 2025 Red Hat, Inc. All Rights Reserved. + * Written by Alessandro Carminati (acarmina@redhat.com) + */ + +#define _FILE_OFFSET_BITS 64 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tests.h" +#include "debug.h" +#include "utils.h" +#include "ram_map.h" +#include "secret.h" + +#define KPROBE_EVENTS_PATH "%s/kprobe_events" +#define KPROBE_EVENTS_ENABLE "%s/events/kprobes/enable" +#define TRACE_PIPE_PATH "%s/trace_pipe" +#define MAX_LINE_LENGTH 256 +#define RETPROBE_NAME "open_retprobe" + +struct open_res { + int open_resv; + bool test_res; +}; +char *tracing_dir; +char *tracingdirs[3] =3D { + NULL, + "/sys/kernel/tracing", + "/sys/kernel/debug/tracing" +}; + +int check_and_set_tracefs_mount(void) +{ + FILE *mounts_file; + char line[256]; + char device[64], mount_point[128], fs_type[32]; + int retval =3D 0; + + mounts_file =3D fopen("/proc/mounts", "r"); + if (mounts_file =3D=3D NULL) { + perror("Failed to open /proc/mounts"); + return 0; // Cannot verify, assume not mounted + } + + while (fgets(line, sizeof(line), mounts_file)) { + if (sscanf(line, "%s %s %s", device, mount_point, fs_type) >=3D 3) { + if (strcmp(mount_point, "/sys/kernel/tracing") =3D=3D 0 && + strcmp(fs_type, "tracefs") =3D=3D 0) { + retval =3D 1; + break; + } + if (strcmp(mount_point, "/sys/kernel/debug/tracing") =3D=3D 0 && + strcmp(fs_type, "tracefs") =3D=3D 0) { + retval =3D 2; + break; + } + } + } + tracing_dir =3D tracingdirs[retval]; + return retval; +} + +int get_device_numbers(int fd, unsigned int *major_num, + unsigned int *minor_num) +{ + struct stat file_stat; + + if (fstat(fd, &file_stat) =3D=3D -1) { + perror("fstat failed"); + return -1; + } + + if (S_ISCHR(file_stat.st_mode) || S_ISBLK(file_stat.st_mode)) { + *major_num =3D major(file_stat.st_rdev); + *minor_num =3D minor(file_stat.st_rdev); + return 0; + } + fprintf(stderr, "File descriptor does not refer to a device file.\n"); + return -1; +} + +static int write_file(const char *path, const char *data) +{ + int fd =3D open(path, O_WRONLY | O_TRUNC); + ssize_t ret; + + if (fd < 0) { + deb_printf("Error opening file %s: %s\n", + path, strerror(errno)); + return -1; + } + deb_printf("echo \"%s\" >%s\n", data, path); + ret =3D write(fd, data, strlen(data)); + close(fd); + if (ret < 0) { + deb_printf("Error writing to file %s: %s\n", + path, strerror(errno)); + return -1; + } + return 0; +} + +static void cleanup_probes(void) +{ + deb_printf("Cleaning up kprobes and tracing...\n"); + char buf[100]; + + sprintf(buf, KPROBE_EVENTS_PATH, tracing_dir); + if (write_file(buf, "\n") !=3D 0) + deb_printf("Failed to clear retprobes. Manual cleanup may be required.\n= "); + + sprintf(buf, KPROBE_EVENTS_ENABLE, tracing_dir); + if (write_file(buf, "0") !=3D 0) + deb_printf("Failed to clear retprobes. Manual cleanup may be required.\n= "); + +} + +static void traced_open(const char *filename, const char *expected_func_na= me, + struct open_res *r) +{ + pid_t child_pid, parent_pid, traced_pid, result; + char retprobe_setup_cmd[MAX_LINE_LENGTH]; + char tmp_path[MAX_LINE_LENGTH]; + char line[MAX_LINE_LENGTH]; + int open_resv, retval =3D -1; + struct open_res res; + int status, timeout; + FILE *trace_file; + time_t start; + int pfd[2]; + int sn; + + r->open_resv =3D -1; + r->test_res =3D false; + + parent_pid =3D getpid(); + + if (pipe(pfd) =3D=3D -1) { + perror("pipe failed"); + return; + } + + deb_printf("Configuring kprobes on '%s'...\n", expected_func_name); + snprintf(tmp_path, sizeof(tmp_path), KPROBE_EVENTS_PATH, tracing_dir); + snprintf(retprobe_setup_cmd, sizeof(retprobe_setup_cmd), + "r2:kprobes/%s_ret %s retval=3D$retval ", RETPROBE_NAME, + expected_func_name); + if (write_file(tmp_path, retprobe_setup_cmd) !=3D 0) { + cleanup_probes(); + return; + } + snprintf(tmp_path, sizeof(tmp_path), KPROBE_EVENTS_ENABLE, + tracing_dir); + if (write_file(tmp_path, "1") !=3D 0) { + cleanup_probes(); + return; + } + + child_pid =3D fork(); + if (child_pid =3D=3D -1) { + deb_printf("fork failed\n"); + cleanup_probes(); + return; + } + + if (child_pid =3D=3D 0) { + close(pfd[0]); + snprintf(line, sizeof(line), TRACE_PIPE_PATH, tracing_dir); + trace_file =3D fopen(line, "r"); + if (!trace_file) { + deb_printf("fopen trace_pipe failed in child\n"); + exit(EXIT_FAILURE); + } + + open_resv =3D -1; + + sleep(2); + while (fgets(line, sizeof(line), trace_file) !=3D NULL) { + traced_pid =3D -1; + deb_printf("Received =3D>%s\n", line); + deb_printf("matching against: RETPROBE_NAME=3D\"%s\" and expected_func_= name=3D\"%s\"\n", + RETPROBE_NAME, expected_func_name); + deb_printf("matching against: RETPROBE_NAME=3D\"%s\" =3D> %p\n", + RETPROBE_NAME, strstr(line, RETPROBE_NAME)); + deb_printf("matching against: expected_func_name=3D\"%s\" =3D>%p\n", + expected_func_name, strstr(line, expected_func_name)); + + if (strstr(line, RETPROBE_NAME) && + strstr(line, expected_func_name)) { + sn =3D sscanf(line, " %*[^-]-%d%*[^=3D]=3D%x", &traced_pid, &open_resv= ); + deb_printf("scanned (%d)traced_pid=3D%d, open_resv=3D%d parent_pid=3D%= d\n", + sn, traced_pid, open_resv, parent_pid); + if (traced_pid =3D=3D parent_pid && open_resv =3D=3D 0) { + deb_printf("found!\n"); + res.open_resv =3D open_resv; + res.test_res =3D true; + write(pfd[1], &res, sizeof(res)); + fclose(trace_file); + exit(EXIT_SUCCESS); + } + } + } + fclose(trace_file); + res.open_resv =3D -1; + res.test_res =3D false; + write(pfd[1], &res, sizeof(res)); + exit(EXIT_FAILURE); + } else { + close(pfd[1]); + sleep(1); + deb_printf("Parent process (PID %d) is calling open()...\n", + parent_pid); + retval =3D open(filename, O_RDONLY); + if (retval =3D=3D -1) { + deb_printf("open failed\n"); + kill(child_pid, SIGTERM); + waitpid(child_pid, NULL, 0); + cleanup_probes(); + return; + } + + start =3D time(NULL); + timeout =3D 15; + + while (1) { + result =3D waitpid(-1, &status, WNOHANG); + if (result =3D=3D -1) { + perror("waitpid"); + break; + } else if (result > 0) { + deb_printf("Child exited normally\n"); + break; + } + + if (time(NULL) - start >=3D timeout) { + printf("Timeout reached! Killing child...\n"); + kill(child_pid, SIGKILL); + waitpid(child_pid, NULL, 0); + break; + } + usleep(100000); + } + + if (read(pfd[0], r, sizeof(struct open_res)) !=3D + sizeof(struct open_res)) { + deb_printf("Failed to read data from child process.\n"); + r->test_res =3D false; + } + + close(pfd[0]); + + cleanup_probes(); + + r->open_resv =3D retval; + if (r->open_resv >=3D 0 && r->test_res) + r->test_res =3D true; + else + r->test_res =3D false; + } +} + +int test_read_at_addr_32bit_ge(struct test_context *t) +{ + if (is_64bit_arch()) { + deb_printf("Skipped (64-bit architecture)\n"); + return SKIPPED; + } + + uint64_t target_addr =3D 0x100000000ULL; + int ret =3D try_read_dev_mem(t->fd, target_addr, 0, NULL); + + if (ret =3D=3D 0) { + deb_printf("PASS: Read beyond 4 GiB at 0x%llx returned 0 bytes\n", + target_addr); + return PASS; + } + deb_printf("FAIL: Expected 0 bytes at 0x%llx, got %d (errno=3D%d)\n", + target_addr, ret, -ret); + return FAIL; +} + +int test_read_outside_linear_map(struct test_context *t) +{ + uint64_t tolerance, start_addr, max_addr, last_linear; + + if (sizeof(void *) =3D=3D 8) { + deb_printf("Skipped: 64-bit architecture\n"); + return SKIPPED; + } + + if (!t->map || t->map->count =3D=3D 0) { + deb_printf("No memory map provided!\n"); + return SKIPPED; + } + + start_addr =3D t->map->regions[0].start; + max_addr =3D t->map->regions[t->map->count - 1].end; + + deb_printf("Scanning between 0x%llx and 0x%llx\n", + (unsigned long long)start_addr, (unsigned long long)max_addr); + + last_linear =3D find_last_linear_byte(t->fd, start_addr, max_addr); + + deb_printf("Last readable linear address: 0x%llx\n", + (unsigned long long)last_linear); + + tolerance =3D 16 * 1024 * 1024; + if (last_linear + 1 >=3D EXPECTED_LINEAR_LIMIT - tolerance && + last_linear + 1 <=3D EXPECTED_LINEAR_LIMIT + tolerance) { + deb_printf("PASS: Linear map ends near 1 GiB boundary.\n"); + return PASS; + } + deb_printf("FAIL: Linear map ends unexpectedly (expected ~890MB).\n"); + return FAIL; +} + +int test_write_outside_linear_map(struct test_context *t) +{ + uint64_t tolerance, start_addr, max_addr, last_linear; + + if (sizeof(void *) =3D=3D 8) { + deb_printf("Skipped: 64-bit architecture\n"); + return SKIPPED; + } + + if (!t->map || t->map->count =3D=3D 0) { + deb_printf("No memory map provided!\n"); + return SKIPPED; + } + + start_addr =3D t->map->regions[0].start; + max_addr =3D t->map->regions[t->map->count - 1].end; + + deb_printf("Scanning between 0x%llx and 0x%llx\n", (unsigned long long)st= art_addr, + (unsigned long long)max_addr); + + last_linear =3D find_last_linear_byte(t->fd, start_addr, max_addr); + + deb_printf("Last readable linear address: 0x%llx\n", + (unsigned long long)last_linear); + + tolerance =3D 16 * 1024 * 1024; + if (last_linear + 1 >=3D EXPECTED_LINEAR_LIMIT - tolerance && + last_linear + 1 <=3D EXPECTED_LINEAR_LIMIT + tolerance) { + deb_printf("PASS: Linear map ends near 1 GiB boundary.\n"); + fill_random_chars(t->srcbuf, BOUNCE_BUF_SIZE); + if (try_write_dev_mem(t->fd, last_linear + 0x1000, + BOUNCE_BUF_SIZE, t->srcbuf) < 0) { + return FAIL; + } + return PASS; + } + deb_printf("FAIL: Linear map ends unexpectedly (expected ~890MB).\n"); + return FAIL; +} + +int test_strict_devmem(struct test_context *t) +{ + int res =3D FAIL; + uint64_t addr; + ssize_t ret; + uint8_t buf; + + addr =3D find_high_system_ram_addr(t->map); + if (addr =3D=3D 0) { + deb_printf("No high System RAM region found.\n"); + res =3D SKIPPED; + return res; + } + + deb_printf("Testing physical address: 0x%llx\n", addr); + + ret =3D pread(t->fd, &buf, 1, addr); + if (ret < 0) { + if (errno =3D=3D EPERM) { + deb_printf("CONFIG_STRICT_DEVMEM is ENABLED\n"); + } else if (errno =3D=3D EFAULT || errno =3D=3D ENXIO) { + deb_printf("Invalid address (errno=3D%d). Try another region.\n", errno= ); + res =3D SKIPPED; + } else if (errno =3D=3D EACCES) { + deb_printf("Access blocked by LSM or lockdown (errno=3DEACCES).\n"); + res =3D SKIPPED; + } else { + perror("pread"); + } + } else { + deb_printf("CONFIG_STRICT_DEVMEM is DISABLED\n"); + res =3D PASS; + } + + if (res !=3D PASS) + t->strict_devmem_state =3D true; + + return res; +} + +int test_devmem_access(struct test_context *t) +{ + struct open_res res; + + if (!check_and_set_tracefs_mount()) { + deb_printf("Tracing directory not found. This test requires debugfs moun= ted.\n"); + return FAIL; + } + + traced_open("/dev/mem", "memory_open", &res); + if ((res.test_res) && (res.open_resv >=3D 0)) { + deb_printf("test_res=3D%d, open_resv=3D%d\n", + res.test_res, res.open_resv); + t->fd =3D res.open_resv; + t->devmem_init_state =3D true; + return PASS; + } + return FAIL; +} + +int test_read_secret_area(struct test_context *t) +{ + void *tmp_ptr; + + deb_printf("\ntest_read_secret_area - start\n"); + tmp_ptr =3D secret_alloc(BOUNCE_BUF_SIZE); + + if (tmp_ptr) { + deb_printf("secret_alloc [ok] tmp_ptr va addr =3D 0x%lx\n", + tmp_ptr); + fill_random_chars(tmp_ptr, BOUNCE_BUF_SIZE); // lazy alloc + if (t->verbose) + print_hex(tmp_ptr, 32); + t->tst_addr =3D virt_to_phys(tmp_ptr); + if (t->tst_addr) { + deb_printf("filled with things -> tst_addr phy addr =3D 0x%lx\n", + t->tst_addr); + if (try_read_dev_mem(t->fd, t->tst_addr, + BOUNCE_BUF_SIZE, t->dstbuf) < 0) + return PASS; + } + } + return FAIL; +} + +int test_read_restricted_area(struct test_context *t) +{ + fill_random_chars(t->dstbuf, BOUNCE_BUF_SIZE); + if (t->verbose) + print_hex(t->dstbuf, 32); + t->tst_addr =3D pick_restricted_address(t->map); + if (t->tst_addr) { + if (try_read_dev_mem(t->fd, t->tst_addr, BOUNCE_BUF_SIZE, + t->dstbuf) >=3D 0) { + if (t->verbose) + print_hex(t->dstbuf, 32); + + if (is_zero(t->dstbuf, BOUNCE_BUF_SIZE)) + return PASS; + + } + } + return FAIL; +} + +int test_read_allowed_area(struct test_context *t) +{ + fill_random_chars(t->srcbuf, BOUNCE_BUF_SIZE); + t->tst_addr =3D virt_to_phys(t->srcbuf); + if (t->tst_addr) { + if (try_read_dev_mem(t->fd, t->tst_addr, BOUNCE_BUF_SIZE, + t->dstbuf) >=3D 0) { + deb_printf("Read OK compare twos\n", t->tst_addr); + if (t->verbose) { + print_hex(t->srcbuf, BOUNCE_BUF_SIZE); + print_hex(t->dstbuf, BOUNCE_BUF_SIZE); + } + if (!memcmp(t->srcbuf, t->dstbuf, BOUNCE_BUF_SIZE)) + return PASS; + } + } + return FAIL; +} + +int test_read_allowed_area_ppos_advance(struct test_context *t) +{ + fill_random_chars(t->srcbuf, BOUNCE_BUF_SIZE); + memset(t->dstbuf, 0, BOUNCE_BUF_SIZE); + if (t->verbose) + print_hex(t->srcbuf, 32); + t->tst_addr =3D virt_to_phys(t->srcbuf); + if (t->tst_addr) { + if ((try_read_dev_mem(t->fd, t->tst_addr, + BOUNCE_BUF_SIZE / 2, t->dstbuf) >=3D 0) && + (try_read_inplace(t->fd, BOUNCE_BUF_SIZE / 2, + t->dstbuf) >=3D 0)){ + if (t->verbose) + print_hex(t->dstbuf, 32); + + if (!memcmp(t->srcbuf + BOUNCE_BUF_SIZE / 2, + t->dstbuf, BOUNCE_BUF_SIZE / 2)) { + return PASS; + } + } + } + return FAIL; +} + +int test_write_outside_area(struct test_context *t) +{ + fill_random_chars(t->srcbuf, BOUNCE_BUF_SIZE); + t->tst_addr =3D pick_outside_address(t->map); + if (try_write_dev_mem(t->fd, t->tst_addr, BOUNCE_BUF_SIZE, + t->srcbuf) < 0) + return PASS; + + return FAIL; +} + +/* + * this test needs to follow test_seek_seek_set + */ +int test_seek_seek_cur(struct test_context *t) +{ + t->tst_addr =3D pick_valid_ram_address(t->map); + if (lseek(t->fd, 0, SEEK_SET) =3D=3D (off_t)-1) + return FAIL; + + if (lseek(t->fd, t->tst_addr, SEEK_CUR) =3D=3D (off_t)-1) + return FAIL; + + return PASS; +} + +int test_seek_seek_set(struct test_context *t) +{ + t->tst_addr =3D pick_valid_ram_address(t->map); + if (lseek(t->fd, t->tst_addr, SEEK_SET) =3D=3D (off_t)-1) + return FAIL; + + return PASS; +} + +int test_seek_seek_other(struct test_context *t) +{ + if (lseek(t->fd, 0, SEEK_END) =3D=3D (off_t)-1) + return PASS; + + return FAIL; +} + +int test_open_devnum(struct test_context *t) +{ + unsigned int major_num, minor_num; + + if (get_device_numbers(t->fd, &major_num, &minor_num) =3D=3D 0) { + if ((major_num =3D=3D 1) && (minor_num =3D=3D 1)) + return PASS; + } + return FAIL; +} diff --git a/tools/testing/selftests/devmem/tests.h b/tools/testing/selftes= ts/devmem/tests.h new file mode 100644 index 000000000000..376412034cde --- /dev/null +++ b/tools/testing/selftests/devmem/tests.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* devmem test tests.h + * + * Copyright (C) 2025 Red Hat, Inc. All Rights Reserved. + * Written by Alessandro Carminati (acarmina@redhat.com) + */ + +#ifndef TESTS_H +#define TESTS_H + +#include "utils.h" + +#define EXPECTED_LINEAR_LIMIT 0x377fe000 +#define PASS 0 +#define FAIL -1 +#define SKIPPED 1 +#define OK_STR "[\e[1;32mPASS\e[0m]" +#define KO_STR "[\e[1;31mFAIL\e[0m]" +#define SKP_STR "[\e[1;33mSKIP\e[0m]" +#define DC_STR "[\e[1;33mDON'T CARE\e[0m]" +#define WARN_STR "\e[1;31mThis shouldn't have happen. Memory is probably c= orrupted!\e[0m" +#define NO_WARN_STR "" + +int test_read_at_addr_32bit_ge(struct test_context *t); +int test_read_outside_linear_map(struct test_context *t); +int test_strict_devmem(struct test_context *t); +int test_devmem_access(struct test_context *t); +int test_read_secret_area(struct test_context *t); +int test_read_allowed_area(struct test_context *t); +int test_read_reserved_area(struct test_context *t); +int test_read_allowed_area(struct test_context *t); +int test_read_allowed_area_ppos_advance(struct test_context *t); +int test_read_restricted_area(struct test_context *t); +int test_write_outside_area(struct test_context *t); +int test_seek_seek_cur(struct test_context *t); +int test_seek_seek_set(struct test_context *t); +int test_seek_seek_other(struct test_context *t); +int test_open_devnum(struct test_context *t); + +static inline bool is_64bit_arch(void) +{ + return sizeof(void *) =3D=3D 8; +} + +#endif diff --git a/tools/testing/selftests/devmem/utils.c b/tools/testing/selftes= ts/devmem/utils.c new file mode 100644 index 000000000000..d14e86dbd81a --- /dev/null +++ b/tools/testing/selftests/devmem/utils.c @@ -0,0 +1,379 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* devmem test utils.c + * + * Copyright (C) 2025 Red Hat, Inc. All Rights Reserved. + * Written by Alessandro Carminati (acarmina@redhat.com) + */ + +#define _FILE_OFFSET_BITS 64 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" +#include "debug.h" + + +static inline uint64_t get_page_size(void) +{ + return (uint64_t)sysconf(_SC_PAGE_SIZE); +} + +uint64_t virt_to_phys(void *virt_addr) +{ + uint64_t virt_pfn, page_size, phys_addr, pfn; + uintptr_t virt =3D (uintptr_t)virt_addr; + ssize_t bytes_read; + uint64_t entry =3D 0; + off_t offset; + int fd; + + page_size =3D get_page_size(); + virt_pfn =3D virt / page_size; + deb_printf("page_size=3D%d, virt_pfn=3D%lu\n", page_size, virt_pfn); + + fd =3D open("/proc/self/pagemap", O_RDONLY); + if (fd < 0) { + deb_printf("Error opening /proc/self/pagemap: %s\n", + strerror(errno)); + return 0; + } + + offset =3D (off_t)(virt_pfn * sizeof(uint64_t)); + deb_printf("lseek(%d, 0x%llx, SEEK_SET)\n", fd, offset); + if (lseek(fd, offset, SEEK_SET) =3D=3D (off_t)-1) { + deb_printf("Error seeking pagemap: %s\n", strerror(errno)); + close(fd); + return 0; + } + + bytes_read =3D read(fd, &entry, sizeof(entry)); + close(fd); + if (bytes_read !=3D sizeof(entry)) { + deb_printf("Error reading pagemap: %s\n", strerror(errno)); + return 0; + } + + if (!(entry & (1ULL << 63))) { + deb_printf("Page not present in RAM (maybe swapped out).\n"); + return 0; + } + + pfn =3D entry & ((1ULL << 55) - 1); + deb_printf("entry=3D%llx, pfn=3D%llx\n", entry, pfn); + if (pfn =3D=3D 0) { + deb_printf("PFN is 0 - invalid mapping.\n"); + return 0; + } + + phys_addr =3D (pfn * page_size) + (virt % page_size); + deb_printf("phys_addr=3D%llx\n", phys_addr); + return phys_addr; +} + +int try_read_inplace(int fd, int scnt, void *sbuf) +{ + ssize_t r; + + r =3D read(fd, sbuf, scnt); + deb_printf("read(%d, %p, %d)=3D%d(%d)\n", fd, sbuf, scnt, r, -errno); + if (r < 0) + return -errno; + + return (int)r; +} + +int try_read_dev_mem(int fd, uint64_t addr, int scnt, void *sbuf) +{ + int space; + ssize_t r; + void *buf; + int cnt; + + buf =3D sbuf ? sbuf : &space; + cnt =3D sbuf ? scnt : sizeof(space); + deb_printf("buf =3D %p, cnt =3D %d\n", buf, cnt); + if (lseek(fd, (off_t)addr, SEEK_SET) =3D=3D (off_t)-1) + return -errno; + + deb_printf("lseek(%d, %llx, SEEK_SET)=3D%d\n", fd, addr, -errno); + + r =3D read(fd, buf, cnt); + deb_printf("read(%d, %p, %d)=3D%d(%d)\n", fd, buf, cnt, r, -errno); + if (r < 0) + return -errno; + + return (int)r; +} + +int try_write_dev_mem(int fd, uint64_t addr, int scnt, void *sbuf) +{ + int space; + ssize_t r; + void *buf; + int cnt; + + buf =3D sbuf ? sbuf : &space; + cnt =3D sbuf ? scnt : sizeof(space); + deb_printf("buf =3D %p, cnt =3D %d\n", buf, cnt); + if (lseek(fd, (off_t)addr, SEEK_SET) =3D=3D (off_t)-1) + return -errno; + + deb_printf("lseek(%d, %llx, SEEK_SET)=3D%d\n", fd, addr, -errno); + + r =3D write(fd, buf, cnt); + deb_printf("write(%d, %p, %d)=3D%d(%d)\n", fd, buf, cnt, r, -errno); + if (r < 0) + return -errno; + + return (int)r; +} + +int fill_random_chars(char *buf, int cnt) +{ + int bytes_read, fd; + ssize_t res; + + if (!buf || cnt <=3D 0) { + errno =3D EINVAL; + return -1; + } + + fd =3D open("/dev/urandom", O_RDONLY); + if (fd < 0) { + perror("open /dev/urandom"); + return -1; + } + + bytes_read =3D 0; + while (bytes_read < cnt) { + res =3D read(fd, buf + bytes_read, cnt - bytes_read); + if (res < 0) { + if (errno =3D=3D EINTR) + continue; + perror("read /dev/urandom"); + close(fd); + return -1; + } + bytes_read +=3D res; + } + close(fd); + + return 0; +} + +bool is_zero(const void *p, size_t cnt) +{ + const char *byte_ptr =3D (const char *)p; + + for (size_t i =3D 0; i < cnt; ++i) { + if (byte_ptr[i] !=3D 0) + return false; + } + return true; +} + +void print_hex(const void *p, size_t cnt) +{ + const unsigned char *bytes =3D (const unsigned char *)p; + int remainder; + size_t i; + + for (i =3D 0; i < cnt; i++) { + if (i % 16 =3D=3D 0) { + if (i > 0) + printf("\n"); + + printf("%08lX: ", (unsigned long)(bytes + i)); + } + printf("%02X ", bytes[i]); + } + + remainder =3D cnt % 16; + if (remainder !=3D 0) { + for (int j =3D 0; j < 16 - remainder; j++) + printf(" "); + } + + printf("\n"); +} + +static bool machine_is_compatible(unsigned int flags) +{ + unsigned int current_arch_flag =3D 0; + unsigned int current_bits_flag =3D 0; + +#if defined(__x86_64__) || defined(__i386__) + current_arch_flag =3D F_ARCH_X86; +#elif defined(__arm__) || defined(__aarch64__) + current_arch_flag =3D F_ARCH_ARM; +#elif defined(__PPC__) || defined(__powerpc__) + current_arch_flag =3D F_ARCH_PPC; +#elif defined(__mips__) + current_arch_flag =3D F_ARCH_MIPS; +#elif defined(__s390__) + current_arch_flag =3D F_ARCH_S390; +#elif defined(__riscv) + current_arch_flag =3D F_ARCH_RISCV; +#else + current_arch_flag =3D 0; +#endif + + if (sizeof(void *) =3D=3D 8) + current_bits_flag =3D F_BITS_B64; + else + current_bits_flag =3D F_BITS_B32; + + bool arch_matches =3D (flags & F_ARCH_ALL) || (flags & current_arch_flag); + + bool bits_matches =3D (flags & F_BITS_ALL) || (flags & current_bits_flag); + + return arch_matches && bits_matches; +} + +static void print_flags(uint32_t flags) +{ + printf("Flags: 0x%08X ->", flags); + + // Architecture flags + printf(" Architecture: "); + if (flags & F_ARCH_ALL) + printf("ALL "); + + if (flags & F_ARCH_X86) + printf("X86 "); + + if (flags & F_ARCH_ARM) + printf("ARM "); + + if (flags & F_ARCH_PPC) + printf("PPC "); + + if (flags & F_ARCH_MIPS) + printf("MIPS "); + + if (flags & F_ARCH_S390) + printf("S390 "); + + if (flags & F_ARCH_RISCV) + printf("RISC-V "); + + // Bitness flags + printf(" Bitness: "); + if (flags & F_BITS_ALL) + printf("ALL "); + + if (flags & F_BITS_B64) + printf("64-bit "); + + if (flags & F_BITS_B32) + printf("32-bit "); + + // Miscellaneous flags + printf(" Miscellaneous:"); + if (flags & F_MISC_FATAL) + printf(" - F_MISC_FATAL: true"); + + if (flags & F_MISC_STRICT_DEVMEM_REQ) + printf(" - F_MISC_STRICT_DEVMEM_REQ: true"); + + if (flags & F_MISC_STRICT_DEVMEM_PRV) + printf(" - F_MISC_STRICT_DEVMEM_PRV: true"); + + if (flags & F_MISC_INIT_PRV) + printf(" - F_MISC_INIT_PRV: true"); + + if (flags & F_MISC_INIT_REQ) + printf(" - F_MISC_INIT_REQ: true"); + + printf("\n"); +} + +static void print_context(struct test_context *t) +{ + char *c; + + c =3D "NO"; + if (t->devmem_init_state) + c =3D "yes"; + printf("system state: init=3D%s, ", c); + c =3D "NO"; + if (t->strict_devmem_state) + c =3D "yes"; + printf("strict_devmem=3D%s\n", c); +} + +int test_needed(struct test_context *t, + struct char_mem_test *current) +{ + if (t->verbose) { + print_context(t); + print_flags(current->flags); + } + + if (!(t->devmem_init_state) && !(current->flags & F_MISC_INIT_PRV)) { + deb_printf("Not initialized and test does not provide initialization\n"); + return TEST_DENIED;// Not initialized and not provide init + } + if ((t->devmem_init_state) && (current->flags & F_MISC_INIT_PRV)) { + deb_printf("can not initialize again\n"); + return TEST_INCOHERENT; // can not initialize again + } + if (!(t->devmem_init_state) && (current->flags & F_MISC_INIT_PRV)) { + deb_printf("initializing: test allowed!\n"); + return TEST_ALLOWED; // initializing: test allowed! + } + if (!(t->devmem_init_state)) { + deb_printf("not initialized, can not proceed\n"); + return TEST_DENIED; // not initialized, can not proceed + } + if (!(machine_is_compatible(current->flags))) { + deb_printf("not for this architecture\n"); + return TEST_DENIED; // not for this architecture + } + if (((t->strict_devmem_state) || (current->flags & + F_MISC_STRICT_DEVMEM_REQ)) && !((t->strict_devmem_state) && + (current->flags & F_MISC_STRICT_DEVMEM_REQ))) { + deb_printf("strict_devmem requirement and offering do not meet\n"); + return TEST_DENIED;// strict_devmem requirement + } + deb_printf("test allowed!\n"); + return TEST_ALLOWED; +} + +void *malloc_pb(size_t size) +{ + if (size =3D=3D 0 || size > getpagesize()) { + fprintf(stderr, "size must be greater than 0 and less than or equal to o= ne page.\n"); + return NULL; + } + + void *ptr =3D mmap(NULL, getpagesize(), PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + + if (ptr =3D=3D MAP_FAILED) { + perror("mmap failed"); + return NULL; + } + + return ptr; +} + +void free_pb(void *ptr) +{ + if (ptr =3D=3D NULL) + return; + + if (munmap(ptr, getpagesize()) =3D=3D -1) + perror("munmap failed"); + +} diff --git a/tools/testing/selftests/devmem/utils.h b/tools/testing/selftes= ts/devmem/utils.h new file mode 100644 index 000000000000..3a8d052f14ba --- /dev/null +++ b/tools/testing/selftests/devmem/utils.h @@ -0,0 +1,119 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* devmem test utils.h + * + * Copyright (C) 2025 Red Hat, Inc. All Rights Reserved. + * Written by Alessandro Carminati (acarmina@redhat.com) + */ + +#ifndef UTIL_H +#define UTIL_H + +#include +#include +#include + +#define BOUNCE_BUF_SIZE 64 +/* + * Test Case Flags: + * F_ARCH_ALL: Test valid on all HW Architectures. + * F_ARCH_X86: Test valid on x86 only. + * F_ARCH_ARM: Test valid on ARM only. + * F_ARCH_PPC: Test valid on PowerPC only. + * F_ARCH_MIPS: Test valid on MIPS only. + * F_ARCH_S390: Test valid on S390 only. + * F_ARCH_RISCV: Test valid on RISC-V only. + * + * F_BITS_ALL: Test valid on both 32b and 64b systems. + * F_BITS_B64: Test valid on 64b systems only. + * F_BITS_B32: Test valid on 32b systems only. + * + * F_MISC_FATAL: a test failure stops the execution of any other test. + * F_MISC_STRICT_DEVMEM_REQ: the test requires STRICT_DEVMEM to be defined + * in the Kernel. + * F_MISC_STRICT_DEVMEM_PRV: the test retrieves the status of STRICT_DEVMEM + * (whether it is defined or not in the Kernel). + * F_MISC_INIT_PRV: the test verify the system to be in a proper init state + * for subsequent tests to run. + * F_MISC_INIT_REQ: the test requires a proper init state as retrieved by + * F_MISC_INIT_PRV. + * F_MISC_DONT_CARE: the test is not part of the test plan, it is just + * auxiliary code that determine how to run other tests. + * F_MISC_WARN_ON_SUCCESS: This flags is applicable to negative tests. I.e. + * it raises a Warning if an operation succeeds wh= en + * it is expected to fail. + * F_MISC_WARN_ON_FAILURE: This flags is applicable to positive tests. I.e. + * it raises a Warning if an operation fails when = it + * is expected to succeed. + */ +#define F_ARCH_ALL 1 +#define F_ARCH_X86 (1 << 1) +#define F_ARCH_ARM (1 << 2) +#define F_ARCH_PPC (1 << 3) +#define F_ARCH_MIPS (1 << 4) +#define F_ARCH_S390 (1 << 5) +#define F_ARCH_RISCV (1 << 6) + +#define F_BITS_ALL (1 << 7) +#define F_BITS_B64 (1 << 8) +#define F_BITS_B32 (1 << 9) + +#define F_MISC_FATAL (1 << 10) +#define F_MISC_STRICT_DEVMEM_REQ (1 << 11) +#define F_MISC_STRICT_DEVMEM_PRV (1 << 12) +#define F_MISC_INIT_PRV (1 << 13) +#define F_MISC_INIT_REQ (1 << 14) +#define F_MISC_DONT_CARE (1 << 15) +#define F_MISC_WARN_ON_SUCCESS (1 << 16) +#define F_MISC_WARN_ON_FAILURE (1 << 17) + +enum { + TEST_DENIED, + TEST_INCOHERENT, + TEST_ALLOWED +}; + +struct test_context { + struct ram_map *map; + char *srcbuf; + char *dstbuf; + uintptr_t tst_addr; + int fd; + bool verbose; + bool strict_devmem_state; + bool devmem_init_state; +}; + +/* + * struct char_mem_test - test case structure for testing /drivers/char/me= m.c + * @name: name of the test case. + * @fn: test callback implementing the test case. + * @descr: test case descriptor; it must be formatted as + * "short description"-"function-name"-"FE" + * where + * "short description" describe what the test case does, + * "function-name" is the name of the tested function in + * /drivers/char/mem.c, + * "FE" is the list of tested Function's Expectations from the + * kernel-doc header associated with "function-name". + * @flags: test case applicable flags (see list above). + */ +struct char_mem_test { + char *name; + int (*fn)(struct test_context *t); + char *descr; + uint64_t flags; +}; + +uint64_t virt_to_phys(void *virt_addr); +int try_read_inplace(int fd, int scnt, void *sbuf); +int try_read_dev_mem(int fd, uint64_t addr, int scnt, void *sbuf); +int try_write_dev_mem(int fd, uint64_t addr, int scnt, void *sbuf); +int fill_random_chars(char *buf, int cnt); +bool is_zero(const void *p, size_t cnt); +void print_hex(const void *p, size_t cnt); +int test_needed(struct test_context *t, struct char_mem_test *current); +void *malloc_pb(size_t size); +void free_pb(void *ptr); + +#endif + --=20 2.48.1